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 2016/09/29 17:38:47 UTC

[01/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Repository: cayenne
Updated Branches:
  refs/heads/master 8c7bec0f4 -> 2f7b1d533


http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/MigrateAction.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/MigrateAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/MigrateAction.java
index ac0ed01..b2fb25b 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/MigrateAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/MigrateAction.java
@@ -19,14 +19,9 @@
 
 package org.apache.cayenne.modeler.action;
 
-import java.awt.event.ActionEvent;
-import java.util.List;
-
-import javax.sql.DataSource;
-import javax.swing.JOptionPane;
-
-import org.apache.cayenne.access.DbLoader;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
+import org.apache.cayenne.dbsync.reverse.DbLoader;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.modeler.Application;
 import org.apache.cayenne.modeler.dialog.db.DataSourceController;
@@ -34,19 +29,24 @@ import org.apache.cayenne.modeler.dialog.db.DbMigrateOptionsDialog;
 import org.apache.cayenne.modeler.dialog.db.MergerOptions;
 import org.apache.cayenne.modeler.pref.DBConnectionInfo;
 
+import javax.sql.DataSource;
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.util.List;
+
 /**
  * Action that alter database schema to match a DataMap.
  */
 public class MigrateAction extends DBWizardAction {
 
-    public static String getActionName() {
-        return "Migrate Database Schema";
-    }
-
     public MigrateAction(Application application) {
         super(getActionName(), application);
     }
 
+    public static String getActionName() {
+        return "Migrate Database Schema";
+    }
+
     public void performAction(ActionEvent e) {
 
         DBConnectionInfo nodeInfo = preferredDataSource();
@@ -71,38 +71,40 @@ public class MigrateAction extends DBWizardAction {
             throw new IllegalStateException("No current DataMap selected.");
         }
         //showOptions dialog
-       String selectedSchema = null;
-       try {
-			List<String> schemas = getSchemas(connectWizard);
-	        if (schemas != null && !schemas.isEmpty()) {
-	        	DbMigrateOptionsDialog optionsDialog = new DbMigrateOptionsDialog(schemas, connectWizard.getConnectionInfo().getUserName());
-	        	optionsDialog.showDialog();
-	        	if (optionsDialog.getChoice() == DbMigrateOptionsDialog.SELECT) {
-	        		selectedSchema = optionsDialog.getSelectedSchema();
-	        	}
-	        }
-		} catch (Exception ex) {
-			JOptionPane.showMessageDialog(
-	                Application.getFrame(),
-	                ex.getMessage(),
-	                "Error loading schemas dialog",
-	                JOptionPane.ERROR_MESSAGE);
-		}
-        
+        String selectedSchema = null;
+        try {
+            List<String> schemas = getSchemas(connectWizard);
+            if (schemas != null && !schemas.isEmpty()) {
+                DbMigrateOptionsDialog optionsDialog = new DbMigrateOptionsDialog(schemas, connectWizard.getConnectionInfo().getUserName());
+                optionsDialog.showDialog();
+                if (optionsDialog.getChoice() == DbMigrateOptionsDialog.SELECT) {
+                    selectedSchema = optionsDialog.getSelectedSchema();
+                }
+            }
+        } catch (Exception ex) {
+            JOptionPane.showMessageDialog(
+                    Application.getFrame(),
+                    ex.getMessage(),
+                    "Error loading schemas dialog",
+                    JOptionPane.ERROR_MESSAGE);
+        }
+
+        MergerTokenFactoryProvider mergerTokenFactoryProvider =
+                getApplication().getInjector().getInstance(MergerTokenFactoryProvider.class);
 
         // ... show dialog...
         new MergerOptions(
                 getProjectController(),
                 "Migrate DB Schema: Options",
                 connectWizard.getConnectionInfo(),
-                map, selectedSchema).startupAction();
+                map, selectedSchema, mergerTokenFactoryProvider).startupAction();
     }
 
     private List<String> getSchemas(DataSourceController connectWizard) throws Exception {
-    	DbAdapter dbAdapter = connectWizard.getConnectionInfo()
-    			.makeAdapter(getApplication().getClassLoadingService());
-    	DataSource dataSource = connectWizard.getConnectionInfo()
-    			.makeDataSource(getApplication().getClassLoadingService());
-    	return new DbLoader(dataSource.getConnection(), dbAdapter, null).getSchemas();
+        DbAdapter dbAdapter = connectWizard.getConnectionInfo()
+                .makeAdapter(getApplication().getClassLoadingService());
+        DataSource dataSource = connectWizard.getConnectionInfo()
+                .makeDataSource(getApplication().getClassLoadingService());
+        return new DbLoader(dataSource.getConnection(), dbAdapter, null).getSchemas();
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/ObjEntitySyncAction.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/ObjEntitySyncAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/ObjEntitySyncAction.java
index d851225..756d7fa 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/ObjEntitySyncAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/ObjEntitySyncAction.java
@@ -19,13 +19,8 @@
 
 package org.apache.cayenne.modeler.action;
 
-import java.awt.Toolkit;
-import java.awt.event.ActionEvent;
-import java.awt.event.KeyEvent;
-
-import javax.swing.KeyStroke;
-
 import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.dbsync.merge.EntityMergeSupport;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.event.EntityEvent;
 import org.apache.cayenne.map.event.MapEvent;
@@ -34,7 +29,11 @@ import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.dialog.objentity.EntitySyncController;
 import org.apache.cayenne.modeler.event.EntityDisplayEvent;
 import org.apache.cayenne.modeler.util.CayenneAction;
-import org.apache.cayenne.util.EntityMergeSupport;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
 
 /**
  * Action that synchronizes a given ObjEntity with the current state of the underlying

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java
index 743d410..92123c0 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/DbLoaderHelper.java
@@ -20,12 +20,13 @@
 package org.apache.cayenne.modeler.dialog.db;
 
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.DbLoader;
-import org.apache.cayenne.access.loader.DefaultDbLoaderDelegate;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dbimport.FiltersConfigBuilder;
 import org.apache.cayenne.dbimport.ReverseEngineering;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
+import org.apache.cayenne.dbsync.reverse.DbLoader;
+import org.apache.cayenne.dbsync.reverse.DefaultDbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.FiltersConfigBuilder;
 import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.map.DataMap;
@@ -47,9 +48,7 @@ import org.apache.cayenne.util.Util;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import javax.swing.JFrame;
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
+import javax.swing.*;
 import java.io.File;
 import java.sql.Connection;
 import java.sql.SQLException;
@@ -375,7 +374,10 @@ public class DbLoaderHelper {
 
 
             ModelerDbImportAction importAction = new ModelerDbImportAction(logObj, DbLoaderHelper.this);
-            Injector injector = DIBootstrap.createInjector(new ToolsModule(logObj), new DbImportModule());
+
+            // TODO: we can keep all these things in the Modeler Injector instead of creating a new one?
+            // we already have CayenneDbSyncModule in there
+            Injector injector = DIBootstrap.createInjector(new CayenneDbSyncModule(), new ToolsModule(logObj), new DbImportModule());
             injector.injectMembers(importAction);
             try {
                 importAction.execute(config);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java
index ab09801..0d07706 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerOptions.java
@@ -19,13 +19,21 @@
 
 package org.apache.cayenne.modeler.dialog.db;
 
-import org.apache.cayenne.access.loader.DbLoaderConfiguration;
-import org.apache.cayenne.access.loader.filters.FiltersConfig;
-import org.apache.cayenne.access.loader.filters.PatternFilter;
-import org.apache.cayenne.access.loader.filters.TableFilter;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.dba.JdbcAdapter;
+import org.apache.cayenne.dbsync.merge.AbstractToDbToken;
+import org.apache.cayenne.dbsync.merge.DbMerger;
+import org.apache.cayenne.dbsync.merge.MergeDirection;
+import org.apache.cayenne.dbsync.merge.MergerContext;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.ModelMergeDelegate;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.dbsync.reverse.DbLoaderConfiguration;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
@@ -35,12 +43,6 @@ import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.ObjRelationship;
 import org.apache.cayenne.map.event.EntityEvent;
 import org.apache.cayenne.map.event.MapEvent;
-import org.apache.cayenne.merge.AbstractToDbToken;
-import org.apache.cayenne.merge.DbMerger;
-import org.apache.cayenne.merge.MergeDirection;
-import org.apache.cayenne.merge.MergerContext;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.ModelMergeDelegate;
 import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.dialog.ValidationResultBrowser;
 import org.apache.cayenne.modeler.event.AttributeDisplayEvent;
@@ -79,11 +81,17 @@ public class MergerOptions extends CayenneController {
     protected DbMerger merger;
     protected MergerTokenSelectorController tokens;
     protected String defaultSchema;
-    
-    public MergerOptions(ProjectController parent, String title,
-            DBConnectionInfo connectionInfo, DataMap dataMap, String defaultSchema) {
+    private MergerTokenFactoryProvider mergerTokenFactoryProvider;
+
+    public MergerOptions(ProjectController parent,
+                         String title,
+                         DBConnectionInfo connectionInfo,
+                         DataMap dataMap,
+                         String defaultSchema,
+                         MergerTokenFactoryProvider mergerTokenFactoryProvider) {
         super(parent);
 
+        this.mergerTokenFactoryProvider = mergerTokenFactoryProvider;
         this.dataMap = dataMap;
         this.tokens = new MergerTokenSelectorController(parent);
         this.view = new MergerOptionsView(tokens.getView());
@@ -124,20 +132,6 @@ public class MergerOptions extends CayenneController {
 
         sqlBinding = builder.bindToTextArea(view.getSql(), "textForSQL");
 
-        /*
-         * optionBindings = new ObjectBinding[5]; optionBindings[0] =
-         * builder.bindToStateChangeAndAction(view.getCreateFK(),
-         * "generatorDefaults.createFK", "refreshSQLAction()"); optionBindings[1] =
-         * builder.bindToStateChangeAndAction(view.getCreatePK(),
-         * "generatorDefaults.createPK", "refreshSQLAction()"); optionBindings[2] =
-         * builder.bindToStateChangeAndAction(view.getCreateTables(),
-         * "generatorDefaults.createTables", "refreshSQLAction()"); optionBindings[3] =
-         * builder.bindToStateChangeAndAction(view.getDropPK(),
-         * "generatorDefaults.dropPK", "refreshSQLAction()"); optionBindings[4] =
-         * builder.bindToStateChangeAndAction(view.getDropTables(),
-         * "generatorDefaults.dropTables", "refreshSQLAction()");
-         */
-
         builder.bindToAction(view.getGenerateButton(), "generateSchemaAction()");
         builder.bindToAction(view.getSaveSqlButton(), "storeSQLAction()");
         builder.bindToAction(view.getCancelButton(), "closeAction()");
@@ -161,8 +155,11 @@ public class MergerOptions extends CayenneController {
     protected void prepareMigrator() {
         try {
             adapter = (JdbcAdapter) connectionInfo.makeAdapter(getApplication().getClassLoadingService());
-            tokens.setMergerFactory(adapter.mergerFactory());
-            merger = new DbMerger(adapter.mergerFactory());
+
+            MergerTokenFactory mergerTokenFactory = mergerTokenFactoryProvider.get(adapter);
+
+            tokens.setMergerTokenFactory(mergerTokenFactory);
+            merger = new DbMerger(mergerTokenFactory);
 
             DbLoaderConfiguration config = new DbLoaderConfiguration();
             config.setFiltersConfig(FiltersConfig.create(null, defaultSchema, TableFilter.everything(), PatternFilter.INCLUDE_NOTHING));
@@ -173,8 +170,7 @@ public class MergerOptions extends CayenneController {
                     dataMap,
                     config);
             tokens.setTokens(mergerTokens);
-        }
-        catch (Exception ex) {
+        } catch (Exception ex) {
             reportError("Error loading adapter", ex);
         }
     }
@@ -451,15 +447,13 @@ public class MergerOptions extends CayenneController {
 
             if (failures == null || !failures.hasFailures()) {
                 JOptionPane.showMessageDialog(getView(), "Migration Complete.");
-            }
-            else {
+            } else {
                 new ValidationResultBrowser(this).startupAction(
                         "Migration Complete",
                         "Migration finished. The following problem(s) were ignored.",
                         failures);
             }
-        }
-        catch (Throwable th) {
+        } catch (Throwable th) {
             reportError("Migration Error", th);
         }
     }
@@ -488,8 +482,7 @@ public class MergerOptions extends CayenneController {
                 pw.print(textForSQL);
                 pw.flush();
                 pw.close();
-            }
-            catch (IOException ex) {
+            } catch (IOException ex) {
                 reportError("Error Saving SQL", ex);
             }
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenSelectorController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenSelectorController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenSelectorController.java
index 35adce2..a5c6e96 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenSelectorController.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenSelectorController.java
@@ -18,27 +18,25 @@
  ****************************************************************/
 package org.apache.cayenne.modeler.dialog.db;
 
-import java.awt.Component;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import org.apache.cayenne.dbsync.merge.MergeDirection;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.modeler.Application;
+import org.apache.cayenne.modeler.util.CayenneController;
+import org.apache.cayenne.swing.BindingBuilder;
+import org.apache.cayenne.swing.ObjectBinding;
 
-import javax.swing.DefaultCellEditor;
-import javax.swing.JComboBox;
+import javax.swing.*;
 import javax.swing.table.AbstractTableModel;
 import javax.swing.table.TableColumn;
 import javax.swing.table.TableColumnModel;
 import javax.swing.table.TableModel;
-
-import org.apache.cayenne.merge.MergeDirection;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.modeler.Application;
-import org.apache.cayenne.modeler.util.CayenneController;
-import org.apache.cayenne.swing.BindingBuilder;
-import org.apache.cayenne.swing.ObjectBinding;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 public class MergerTokenSelectorController extends CayenneController {
 
@@ -49,7 +47,7 @@ public class MergerTokenSelectorController extends CayenneController {
     protected int permanentlyExcludedCount;
     protected Set<MergerToken> excludedTokens;
     protected List<MergerToken> selectableTokensList;
-    protected MergerFactory mergerFactory;
+    protected MergerTokenFactory mergerTokenFactory;
 
     public MergerTokenSelectorController(CayenneController parent) {
         super(parent);
@@ -59,8 +57,8 @@ public class MergerTokenSelectorController extends CayenneController {
         initController();
     }
 
-    public void setMergerFactory(MergerFactory mergerFactory) {
-        this.mergerFactory = mergerFactory;
+    public void setMergerTokenFactory(MergerTokenFactory mergerTokenFactory) {
+        this.mergerTokenFactory = mergerTokenFactory;
     }
 
     public void setTokens(List<MergerToken> tokens) {
@@ -197,7 +195,7 @@ public class MergerTokenSelectorController extends CayenneController {
             return;
         }
         int i = selectableTokensList.indexOf(token);
-        MergerToken reverse = token.createReverse(mergerFactory);
+        MergerToken reverse = token.createReverse(mergerTokenFactory);
         selectableTokensList.set(i, reverse);
         if (excludedTokens.remove(token)) {
             excludedTokens.add(reverse);
@@ -228,7 +226,7 @@ public class MergerTokenSelectorController extends CayenneController {
         
         for (int i = 0; i < selectableTokensList.size(); i++) {
             MergerToken token = selectableTokensList.get(i);
-            MergerToken reverse = token.createReverse(mergerFactory);
+            MergerToken reverse = token.createReverse(mergerTokenFactory);
             selectableTokensList.set(i, reverse);
             if (excludedTokens.remove(token)) {
                 excludedTokens.add(reverse);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenTableModel.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenTableModel.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenTableModel.java
index f69d43d..488b1e8 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenTableModel.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/MergerTokenTableModel.java
@@ -18,12 +18,12 @@
  ****************************************************************/
 package org.apache.cayenne.modeler.dialog.db;
 
-import java.util.List;
+import org.apache.cayenne.dbsync.merge.MergeDirection;
+import org.apache.cayenne.dbsync.merge.MergerToken;
 
 import javax.swing.table.AbstractTableModel;
+import java.util.List;
 
-import org.apache.cayenne.merge.MergeDirection;
-import org.apache.cayenne.merge.MergerToken;
 
 public class MergerTokenTableModel extends AbstractTableModel {
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
index 3be2ba3..b7e6ce2 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ModelerDbImportAction.java
@@ -23,6 +23,7 @@ import org.apache.cayenne.configuration.event.DataMapEvent;
 import org.apache.cayenne.configuration.server.DataSourceFactory;
 import org.apache.cayenne.configuration.server.DbAdapterFactory;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.MapLoader;
@@ -57,6 +58,9 @@ public class ModelerDbImportAction implements DbImportAction {
     @Inject
     private MapLoader mapLoader;
 
+    @Inject
+    private MergerTokenFactoryProvider mergerTokenFactoryProvider;
+
     public ModelerDbImportAction(Log logger, DbLoaderHelper dbLoaderHelper) {
         this.logger = logger;
         this.dbLoaderHelper = dbLoaderHelper;
@@ -68,7 +72,7 @@ public class ModelerDbImportAction implements DbImportAction {
             throw new IllegalStateException("Before using execute method you must set dbLoaderHelper");
         }
 
-        new DefaultDbImportAction(logger, projectSaver, dataSourceFactory, adapterFactory, mapLoader) {
+        new DefaultDbImportAction(logger, projectSaver, dataSourceFactory, adapterFactory, mapLoader, mergerTokenFactoryProvider) {
 
             @Override
             protected DataMap loadExistingDataMap(File dataMapFile) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java
index b2ac550..e83a36a 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/db/ReverseEngineeringController.java
@@ -18,14 +18,14 @@
  ****************************************************************/
 package org.apache.cayenne.modeler.dialog.db;
 
-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.filters.CatalogFilter;
-import org.apache.cayenne.access.loader.filters.SchemaFilter;
 import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dbimport.FiltersConfigBuilder;
 import org.apache.cayenne.dbimport.ReverseEngineering;
+import org.apache.cayenne.dbsync.reverse.DbLoader;
+import org.apache.cayenne.dbsync.reverse.DbLoaderConfiguration;
+import org.apache.cayenne.dbsync.reverse.DefaultDbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.FiltersConfigBuilder;
+import org.apache.cayenne.dbsync.reverse.filters.CatalogFilter;
+import org.apache.cayenne.dbsync.reverse.filters.SchemaFilter;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
@@ -49,9 +49,8 @@ import org.apache.cayenne.swing.ObjectBinding;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-import javax.swing.DefaultComboBoxModel;
-import javax.swing.SwingUtilities;
-import java.awt.Component;
+import javax.swing.*;
+import java.awt.*;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.Arrays;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/EntitySyncController.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/EntitySyncController.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/EntitySyncController.java
index 6e07559..2941159 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/EntitySyncController.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/dialog/objentity/EntitySyncController.java
@@ -19,19 +19,18 @@
 
 package org.apache.cayenne.modeler.dialog.objentity;
 
-import java.awt.Component;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.Collection;
-import java.util.Collections;
-
+import org.apache.cayenne.dbsync.merge.EntityMergeSupport;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.naming.DefaultNameGenerator;
 import org.apache.cayenne.map.naming.ObjectNameGenerator;
 import org.apache.cayenne.modeler.util.CayenneController;
 import org.apache.cayenne.modeler.util.NameGeneratorPreferences;
-import org.apache.cayenne.util.EntityMergeSupport;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.Collections;
 
 public class EntitySyncController extends CayenneController {
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/CayenneGeneratorMojo.java
----------------------------------------------------------------------
diff --git a/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/CayenneGeneratorMojo.java b/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/CayenneGeneratorMojo.java
index 87a3156..ec990cc 100644
--- a/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/CayenneGeneratorMojo.java
+++ b/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/CayenneGeneratorMojo.java
@@ -19,7 +19,7 @@
 
 package org.apache.cayenne.tools;
 
-import org.apache.cayenne.access.loader.NamePatternMatcher;
+import org.apache.cayenne.dbsync.reverse.NamePatternMatcher;
 import org.apache.cayenne.gen.ClassGenerationAction;
 import org.apache.cayenne.gen.ClientClassGenerationAction;
 import org.apache.cayenne.map.DataMap;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java
----------------------------------------------------------------------
diff --git a/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java b/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java
index e15c65e..99a649f 100644
--- a/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java
+++ b/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbGeneratorMojo.java
@@ -19,13 +19,11 @@
 
 package org.apache.cayenne.tools;
 
-import java.io.File;
-import java.sql.Driver;
-
 import org.apache.cayenne.access.DbGenerator;
 import org.apache.cayenne.datasource.DriverDataSource;
 import org.apache.cayenne.dba.DbAdapter;
 import org.apache.cayenne.dba.JdbcAdapter;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
@@ -40,6 +38,9 @@ import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 import org.xml.sax.InputSource;
 
+import java.io.File;
+import java.sql.Driver;
+
 /**
  * Maven mojo to perform class generation from data map. This class is a Maven
  * adapter to DefaultClassGenerator class.
@@ -141,7 +142,7 @@ public class DbGeneratorMojo extends AbstractMojo {
     public void execute() throws MojoExecutionException, MojoFailureException {
 
         Log logger = new MavenLogger(this);
-        Injector injector = DIBootstrap.createInjector(new ToolsModule(logger));
+        Injector injector = DIBootstrap.createInjector(new CayenneDbSyncModule(), new ToolsModule(logger));
         AdhocObjectFactory objectFactory = injector.getInstance(AdhocObjectFactory.class);
 
         logger.info(String.format("connection settings - [driver: %s, url: %s, username: %s]", driver, url, username));

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbImporterMojo.java
----------------------------------------------------------------------
diff --git a/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbImporterMojo.java b/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbImporterMojo.java
index 3e05c5f..7c8872b 100644
--- a/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbImporterMojo.java
+++ b/plugins/maven-cayenne-plugin/src/main/java/org/apache/cayenne/tools/DbImporterMojo.java
@@ -18,19 +18,19 @@
  ****************************************************************/
 package org.apache.cayenne.tools;
 
-import org.apache.cayenne.access.loader.filters.LegacyFilterConfigBridge;
 import org.apache.cayenne.configuration.ConfigurationNameMapper;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.XMLDataMapLoader;
 import org.apache.cayenne.configuration.server.DataSourceFactory;
 import org.apache.cayenne.configuration.server.DbAdapterFactory;
 import org.apache.cayenne.dba.DbAdapter;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.apache.cayenne.dbimport.*;
+import org.apache.cayenne.dbimport.Catalog;
+import org.apache.cayenne.dbimport.DefaultReverseEngineeringLoader;
+import org.apache.cayenne.dbimport.ReverseEngineering;
+import org.apache.cayenne.dbimport.Schema;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
+import org.apache.cayenne.dbsync.reverse.FiltersConfigBuilder;
+import org.apache.cayenne.dbsync.reverse.filters.LegacyFilterConfigBridge;
 import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.map.DataMap;
@@ -47,6 +47,9 @@ import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
 
 import javax.sql.DataSource;
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
 
 /**
  * Maven mojo to reverse engineer datamap from DB.
@@ -276,7 +279,7 @@ public class DbImporterMojo extends AbstractMojo {
         File dataMapFile = config.getDataMapFile();
 
         if (isReverseEngineeringDefined) {
-            Injector injector = DIBootstrap.createInjector(new ToolsModule(logger), new DbImportModule());
+            Injector injector = DIBootstrap.createInjector(new CayenneDbSyncModule(), new ToolsModule(logger), new DbImportModule());
 
             validateDbImportConfiguration(config, injector);
 
@@ -304,7 +307,7 @@ public class DbImporterMojo extends AbstractMojo {
                     DataMap dataMap = xmlDataMapLoader.load(resource);
                     if (dataMap.getReverseEngineering() != null) {
                         try {
-                            Injector injector = DIBootstrap.createInjector(new ToolsModule(logger), new DbImportModule());
+                            Injector injector = DIBootstrap.createInjector(new CayenneDbSyncModule(), new ToolsModule(logger), new DbImportModule());
                             ConfigurationNameMapper nameMapper = injector.getInstance(ConfigurationNameMapper.class);
                             String reverseEngineeringLocation = nameMapper.configurationLocation(ReverseEngineering.class, dataMap.getReverseEngineering().getName());
                             Resource reverseEngineeringResource = new URLResource(dataMapFile.toURI().toURL()).getRelativeResource(reverseEngineeringLocation);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/plugins/maven-cayenne-plugin/src/test/java/org/apache/cayenne/tools/DbImporterMojoConfigurationTest.java
----------------------------------------------------------------------
diff --git a/plugins/maven-cayenne-plugin/src/test/java/org/apache/cayenne/tools/DbImporterMojoConfigurationTest.java b/plugins/maven-cayenne-plugin/src/test/java/org/apache/cayenne/tools/DbImporterMojoConfigurationTest.java
index 7061916..f76eb90 100644
--- a/plugins/maven-cayenne-plugin/src/test/java/org/apache/cayenne/tools/DbImporterMojoConfigurationTest.java
+++ b/plugins/maven-cayenne-plugin/src/test/java/org/apache/cayenne/tools/DbImporterMojoConfigurationTest.java
@@ -20,10 +20,10 @@ package org.apache.cayenne.tools;
 
 import static org.apache.cayenne.dbimport.DefaultReverseEngineeringLoaderTest.*;
 
-import org.apache.cayenne.access.loader.filters.FiltersConfig;
-import org.apache.cayenne.access.loader.filters.IncludeTableFilter;
-import org.apache.cayenne.access.loader.filters.PatternFilter;
-import org.apache.cayenne.access.loader.filters.TableFilter;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.IncludeTableFilter;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
 import org.apache.cayenne.tools.dbimport.DbImportConfiguration;
 import org.apache.cayenne.dbimport.Schema;
 import org.apache.maven.plugin.testing.AbstractMojoTestCase;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index ba88917..3632bf2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -53,6 +53,7 @@
 		<module>cayenne-di</module>
 		<module>cayenne-server</module>
 		<module>cayenne-client</module>
+		<module>cayenne-dbsync</module>
 		<module>cayenne-tools</module>
 		<module>cayenne-project</module>
 		<module>cayenne-lifecycle</module>


[06/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseMergerFactory.java
deleted file mode 100644
index 9750aeb..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseMergerFactory.java
+++ /dev/null
@@ -1,144 +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.dba.openbase;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.merge.CreateTableToDb;
-import org.apache.cayenne.merge.DropRelationshipToDb;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-import org.apache.cayenne.merge.SetNotNullToDb;
-
-public class OpenBaseMergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createCreateTableToDb(DbEntity entity) {
-        return new CreateTableToDb(entity) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                List<String> sqls = new ArrayList<String>();
-                // create table first as OpenBase adapter created primary key in its
-                // getPkGenerator().createAutoPkStatements
-                sqls.add(adapter.createTable(getEntity()));
-                sqls.addAll(adapter.getPkGenerator().createAutoPkStatements(
-                        Collections.singletonList(getEntity())));
-                return sqls;
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createDropRelationshipToDb(
-            final DbEntity entity,
-            final DbRelationship rel) {
-        return new DropRelationshipToDb(entity, rel) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-
-
-                // FK_NAME form jdbc metadata seem to be wrong. It contain a column name
-                // and not the 'relationshipName'
-                // TODO: tell openbase developer mail list
-
-                DbEntity source = getEntity();
-                DbEntity dest = rel.getTargetEntity();
-
-                // only use the first. See adapter
-                // TODO: can we be sure this is the first and same as used by the adapter?
-                DbJoin join = rel.getJoins().get(0);
-
-                // see comment in adapter for why source and dest is switched around..
-
-                return Collections.singletonList("delete from _SYS_RELATIONSHIP where "
-                        + " source_table = '" + dest.getFullyQualifiedName() + "'"
-                        + " and source_column = '" + join.getTargetName() + "'"
-                        + " and dest_table = '" + source.getFullyQualifiedName() + "'"
-                        + " and dest_column = '" + join.getSourceName() + "'");
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetColumnTypeToDb(
-            final DbEntity entity,
-            final DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                List<String> sqls = new ArrayList<String>();
-
-                if (columnOriginal.getMaxLength() != columnNew.getMaxLength()) {
-                    sqls.add("ALTER TABLE "
-                            + entity.getFullyQualifiedName()
-                            + " COLUMN "
-                            + columnNew.getName()
-                            + " SET LENGTH "
-                            + columnNew.getMaxLength());
-                }
-
-                return sqls;
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetNotNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-
-                return Collections.singletonList("ALTER TABLE " + getEntity().getFullyQualifiedName()
-                        + " COLUMN " + getColumn().getName() + " SET NOT NULL");
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-
-                return Collections.singletonList("ALTER TABLE " + getEntity().getFullyQualifiedName()
-                        + " COLUMN " + getColumn().getName() + " SET NULL");
-            }
-
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
index adfd6dd..668676f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleAdapter.java
@@ -26,7 +26,11 @@ import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.SelectTranslator;
-import org.apache.cayenne.access.types.*;
+import org.apache.cayenne.access.types.ByteType;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ShortType;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -35,12 +39,20 @@ import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.query.*;
+import org.apache.cayenne.query.BatchQuery;
+import org.apache.cayenne.query.InsertBatchQuery;
+import org.apache.cayenne.query.Query;
+import org.apache.cayenne.query.SQLAction;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.query.UpdateBatchQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
 import java.lang.reflect.Field;
-import java.sql.*;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -330,9 +342,4 @@ public class OracleAdapter extends JdbcAdapter {
 			return st.wasNull() ? null : i == 0 ? Boolean.FALSE : Boolean.TRUE;
 		}
 	}
-
-	@Override
-	public MergerFactory mergerFactory() {
-		return new OracleMergerFactory();
-	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleMergerFactory.java
deleted file mode 100644
index 7b52185..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/oracle/OracleMergerFactory.java
+++ /dev/null
@@ -1,112 +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.dba.oracle;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.AddColumnToDb;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-import org.apache.cayenne.merge.SetNotNullToDb;
-
-
-public class OracleMergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createAddColumnToDb(final DbEntity entity, final DbAttribute column) {
-        return new AddColumnToDb(entity, column) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" ADD ");
-                sqlBuffer.append(context.quotedName(column));
-                sqlBuffer.append(" ");
-            }
-        };
-    }
-    
-    @Override
-    public MergerToken createSetColumnTypeToDb(
-            final DbEntity entity,
-            DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" MODIFY ");
-                sqlBuffer.append(context.quotedName(columnNew));
-                sqlBuffer.append(" ");
-            }
-        };
-    }
-    
-    @Override
-    public MergerToken createSetAllowNullToDb(DbEntity entity, final DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuffer sqlBuffer = new StringBuffer();
-
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" MODIFY ");
-
-                adapter.createTableAppendColumn(sqlBuffer, column);
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetNotNullToDb(DbEntity entity, final DbAttribute column) {
-
-        return new SetNotNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuffer sqlBuffer = new StringBuffer();
-
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" MODIFY ");
-
-                adapter.createTableAppendColumn(sqlBuffer, column);
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
index 7df4354..98c9944 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresAdapter.java
@@ -39,7 +39,6 @@ import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SelectQuery;
@@ -272,11 +271,6 @@ public class PostgresAdapter extends JdbcAdapter {
 	}
 
 	@Override
-	public MergerFactory mergerFactory() {
-		return new PostgresMergerFactory();
-	}
-
-	@Override
 	public boolean supportsCatalogsOnReverseEngineering() {
 		return false;
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresMergerFactory.java
deleted file mode 100644
index 6e5db58..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/postgres/PostgresMergerFactory.java
+++ /dev/null
@@ -1,50 +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.dba.postgres;
-
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-
-public class PostgresMergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createSetColumnTypeToDb(
-            final DbEntity entity,
-            DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy quotingStrategy) {
-                // http://www.postgresql.org/docs/8.2/static/sql-altertable.html
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(quotingStrategy.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" ALTER ");
-                sqlBuffer.append(quotingStrategy.quotedName(columnNew));
-                sqlBuffer.append(" TYPE ");
-            }
-        };
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
index 537296d..3faec45 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerAdapter.java
@@ -31,7 +31,6 @@ import org.apache.cayenne.dba.sybase.SybaseAdapter;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SelectQuery;
@@ -138,9 +137,4 @@ public class SQLServerAdapter extends SybaseAdapter {
 		}
 	}
 
-	@Override
-	public MergerFactory mergerFactory() {
-		return new SQLServerMergerFactory();
-	}
-
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerMergerFactory.java
deleted file mode 100644
index 202f09c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sqlserver/SQLServerMergerFactory.java
+++ /dev/null
@@ -1,114 +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.dba.sqlserver;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.AddColumnToDb;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-import org.apache.cayenne.merge.SetNotNullToDb;
-
-public class SQLServerMergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createSetColumnTypeToDb(
-            final DbEntity entity,
-            DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                // http://msdn2.microsoft.com/en-us/library/ms190273.aspx
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" ALTER COLUMN ");
-                sqlBuffer.append(context.quotedName(columnNew));
-                sqlBuffer.append(" ");
-            }
-        };
-    }
-
-    @Override
-    public MergerToken createAddColumnToDb(final DbEntity entity, final DbAttribute column) {
-        return new AddColumnToDb(entity, column) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                // http://msdn2.microsoft.com/en-us/library/ms190273.aspx
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" ADD ");
-                sqlBuffer.append(context.quotedName(column));
-                sqlBuffer.append(" ");
-            }
-        };
-    }
-
-    @Override
-    public MergerToken createSetAllowNullToDb(DbEntity entity, final DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuffer sqlBuffer = new StringBuffer();
-
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" ALTER COLUMN ");
-
-                adapter.createTableAppendColumn(sqlBuffer, column);
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetNotNullToDb(DbEntity entity, final DbAttribute column) {
-
-        return new SetNotNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuffer sqlBuffer = new StringBuffer();
-
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" ALTER COLUMN ");
-
-                adapter.createTableAppendColumn(sqlBuffer, column);
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
index 2b0ca86..073290a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseAdapter.java
@@ -21,7 +21,13 @@ package org.apache.cayenne.dba.sybase;
 
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
-import org.apache.cayenne.access.types.*;
+import org.apache.cayenne.access.types.ByteArrayType;
+import org.apache.cayenne.access.types.ByteType;
+import org.apache.cayenne.access.types.CharType;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ShortType;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.DefaultQuotingStrategy;
@@ -29,7 +35,6 @@ import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.dba.QuotingStrategy;
 import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.resource.ResourceLocator;
 
 import java.sql.PreparedStatement;
@@ -122,9 +127,4 @@ public class SybaseAdapter extends JdbcAdapter {
             super.bindParameter(statement, binding);
         }
     }
-
-    @Override
-    public MergerFactory mergerFactory() {
-        return new SybaseMergerFactory();
-    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseMergerFactory.java
deleted file mode 100644
index b3d204f..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/sybase/SybaseMergerFactory.java
+++ /dev/null
@@ -1,167 +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.dba.sybase;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.AddColumnToDb;
-import org.apache.cayenne.merge.DropColumnToDb;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-import org.apache.cayenne.merge.SetNotNullToDb;
-
-/**
- * @since 3.0
- */
-public class SybaseMergerFactory extends MergerFactory {
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public MergerToken createAddColumnToDb(DbEntity entity, final DbAttribute column) {
-        return new AddColumnToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-
-                StringBuffer sqlBuffer = new StringBuffer();
-                QuotingStrategy context = adapter.getQuotingStrategy();
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" ADD ");
-                boolean magnatory = column.isMandatory();
-                column.setMandatory(false);
-                adapter.createTableAppendColumn(sqlBuffer, column);
-                if(magnatory){
-                    column.setMandatory(magnatory);
-                }
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
-        return new DropColumnToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuilder sqlBuffer = new StringBuilder();
-                QuotingStrategy context = adapter.getQuotingStrategy();
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" DROP ");
-                sqlBuffer.append(context.quotedName(getColumn()));
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetNotNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-
-                StringBuffer sqlBuffer = createStringQuery(
-                        adapter,
-                        getEntity(),
-                        getColumn());
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuffer sqlBuffer = createStringQuery(
-                        adapter,
-                        getEntity(),
-                        getColumn());
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-        };
-    }
-
-    /**
-     * @since 3.0
-     */
-    @Override
-    public MergerToken createSetColumnTypeToDb(
-            final DbEntity entity,
-            DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                // http://dev.mysql.com/tech-resources/articles/mysql-cluster-50.html
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" MODIFY ");
-                sqlBuffer.append(context.quotedName(columnNew));
-                sqlBuffer.append(" ");
-            }
-
-        };
-    }
-
-    private static StringBuffer createStringQuery(
-            DbAdapter adapter,
-            DbEntity entity,
-            DbAttribute column) {
-        StringBuffer sqlBuffer = new StringBuffer();
-        QuotingStrategy context = adapter.getQuotingStrategy();
-        sqlBuffer.append("ALTER TABLE ");
-        sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-        sqlBuffer.append(" MODIFY ");
-        adapter.createTableAppendColumn(sqlBuffer, column);
-
-        return sqlBuffer;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dbimport/FiltersConfigBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dbimport/FiltersConfigBuilder.java b/cayenne-server/src/main/java/org/apache/cayenne/dbimport/FiltersConfigBuilder.java
deleted file mode 100644
index d7c4d9d..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dbimport/FiltersConfigBuilder.java
+++ /dev/null
@@ -1,383 +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
- * <p/>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p/>
- * 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.dbimport;
-
-import org.apache.cayenne.access.loader.filters.LegacyFilterConfigBridge;
-import org.apache.cayenne.access.loader.filters.CatalogFilter;
-import org.apache.cayenne.access.loader.filters.IncludeTableFilter;
-import org.apache.cayenne.access.loader.filters.SchemaFilter;
-import org.apache.cayenne.access.loader.filters.TableFilter;
-import org.apache.cayenne.access.loader.filters.FiltersConfig;
-import org.apache.cayenne.access.loader.filters.PatternFilter;
-
-import java.util.*;
-import java.util.regex.Pattern;
-
-import static org.apache.commons.lang.StringUtils.isBlank;
-
-/**
- * @since 4.0.
- */
-public final class FiltersConfigBuilder {
-
-    private final ReverseEngineering engineering;
-
-    public FiltersConfigBuilder(ReverseEngineering engineering) {
-        this.engineering = engineering;
-    }
-
-    public FiltersConfig filtersConfig() {
-        compact();
-
-        return new FiltersConfig(transformCatalogs(engineering.getCatalogs()));
-    }
-
-    private CatalogFilter[] transformCatalogs(Collection<Catalog> catalogs) {
-        CatalogFilter[] catalogFilters = new CatalogFilter[catalogs.size()];
-        int i = 0;
-        for (Catalog catalog : catalogs) {
-            catalogFilters[i] = new CatalogFilter(catalog.getName(), transformSchemas(catalog.getSchemas()));
-            i++;
-        }
-
-        return catalogFilters;
-    }
-
-    private SchemaFilter[] transformSchemas(Collection<Schema> schemas) {
-        SchemaFilter[] schemaFilters = new SchemaFilter[schemas.size()];
-        int i = 0;
-        for (Schema schema : schemas) {
-            schemaFilters[i] = new SchemaFilter(schema.getName(),
-                    new TableFilter(transformIncludeTable(schema.getIncludeTables()),
-                            transformExcludeTable(schema.getExcludeTables())),
-                    transform(schema.getIncludeProcedures(), schema.getExcludeProcedures()));
-            i++;
-        }
-
-        return schemaFilters;
-    }
-
-    private SortedSet<Pattern> transformExcludeTable(Collection<ExcludeTable> excludeTables) {
-        SortedSet<Pattern> res = new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR);
-        for (ExcludeTable exclude : excludeTables) {
-            res.add(PatternFilter.pattern(exclude.getPattern()));
-        }
-        return res;
-    }
-
-    private SortedSet<IncludeTableFilter> transformIncludeTable(Collection<IncludeTable> includeTables) {
-        SortedSet<IncludeTableFilter> includeTableFilters = new TreeSet<IncludeTableFilter>();
-        for (IncludeTable includeTable : includeTables) {
-            includeTableFilters.add(new IncludeTableFilter(includeTable.getPattern(),
-                    transform(includeTable.getIncludeColumns(), includeTable.getExcludeColumns())));
-        }
-
-        return includeTableFilters;
-    }
-
-    private PatternFilter transform(Collection<? extends PatternParam> include,
-                                    Collection<? extends PatternParam> exclude) {
-        PatternFilter filter = new PatternFilter();
-
-        for (PatternParam patternParam : include) {
-            filter.include(patternParam.getPattern());
-        }
-
-        for (PatternParam patternParam : exclude) {
-            filter.exclude(patternParam.getPattern());
-        }
-
-        return filter;
-
-    }
-
-    /**
-     * Goal of this method transform ReverseEngineering config into more regular form
-     * From
-     *      ReverseEngineering
-     *          Catalog
-     *              Schema
-     *                  IncludeTable
-     *                      IncludeColumn
-     *                      ExcludeColumn
-     *                  ExcludeTable
-     *                  IncludeProcedures
-     *                  ExcludeProcedures
-     *                  IncludeColumn
-     *                  ExcludeColumn
-     *              IncludeTable
-     *                  IncludeColumn
-     *                  ExcludeColumn
-     *              ExcludeTable
-     *              IncludeProcedures
-     *              ExcludeProcedures
-     *              IncludeColumn
-     *              ExcludeColumn
-     *          Schema
-     *              IncludeTable
-     *                  IncludeColumn
-     *                  ExcludeColumn
-     *              ExcludeTable
-     *              IncludeProcedures
-     *              ExcludeProcedures
-     *              IncludeColumn
-     *              ExcludeColumn
-     *          IncludeTable
-     *              IncludeColumn
-     *              ExcludeColumn
-     *          ExcludeTable
-     *          IncludeProcedures
-     *          ExcludeProcedures
-     *          IncludeColumn
-     *          ExcludeColumn
-     *
-     * Into
-     *      ReverseEngineering
-     *          Catalog
-     *              Schema
-     *                  IncludeTable
-     *                      IncludeColumn
-     *                      ExcludeColumn
-     *                  ExcludeTable
-     *                  IncludeProcedures
-     *                  ExcludeProcedures
-     *
-     *
-     * */
-    public void compact() {
-        addEmptyElements();
-
-        compactColumnFilters();
-        compactTableFilter();
-        compactProcedureFilter();
-        compactSchemas();
-    }
-
-    private void compactSchemas() {
-        for (Catalog catalog : engineering.getCatalogs()) {
-            catalog.getSchemas().addAll(engineering.getSchemas());
-        }
-        engineering.setSchemas(null);
-    }
-
-    private void compactProcedureFilter() {
-        Collection<IncludeProcedure> engIncludeProcedures = engineering.getIncludeProcedures();
-        Collection<ExcludeProcedure> engExcludeProcedures = engineering.getExcludeProcedures();
-
-        engineering.setIncludeProcedures(null);
-        engineering.setExcludeProcedures(null);
-
-        for (Catalog catalog : engineering.getCatalogs()) {
-            Collection<IncludeProcedure> catalogIncludeProcedures = catalog.getIncludeProcedures();
-            Collection<ExcludeProcedure> catalogExcludeProcedures = catalog.getExcludeProcedures();
-
-            catalog.setIncludeProcedures(null);
-            catalog.setExcludeProcedures(null);
-
-            for (Schema schema : catalog.getSchemas()) {
-                if (engIncludeProcedures != null) {
-                    schema.getIncludeProcedures().addAll(engIncludeProcedures);
-                    schema.getIncludeProcedures().addAll(catalogIncludeProcedures);
-                }
-                if (engExcludeProcedures != null) {
-                    schema.getExcludeProcedures().addAll(engExcludeProcedures);
-                    schema.getExcludeProcedures().addAll(catalogExcludeProcedures);
-                }
-            }
-        }
-
-        for (Schema schema : engineering.getSchemas()) {
-            schema.getIncludeProcedures().addAll(engIncludeProcedures);
-            schema.getExcludeProcedures().addAll(engExcludeProcedures);
-        }
-    }
-
-    private void compactTableFilter() {
-        Collection<IncludeTable> engIncludeTables = engineering.getIncludeTables();
-        Collection<ExcludeTable> engExcludeTables = engineering.getExcludeTables();
-
-        engineering.setIncludeTables(null);
-        engineering.setExcludeTables(null);
-
-        for (Catalog catalog : engineering.getCatalogs()) {
-            Collection<IncludeTable> catalogIncludeTables = catalog.getIncludeTables();
-            Collection<ExcludeTable> catalogExcludeTables = catalog.getExcludeTables();
-
-            catalog.setIncludeTables(null);
-            catalog.setExcludeTables(null);
-
-            for (Schema schema : catalog.getSchemas()) {
-                if (engIncludeTables != null) {
-                    schema.getIncludeTables().addAll(engIncludeTables);
-                    schema.getIncludeTables().addAll(catalogIncludeTables);
-                }
-                if (engExcludeTables != null) {
-                    schema.getExcludeTables().addAll(engExcludeTables);
-                    schema.getExcludeTables().addAll(catalogExcludeTables);
-                }
-            }
-        }
-
-        for (Schema schema : engineering.getSchemas()) {
-            schema.getIncludeTables().addAll(engIncludeTables);
-            schema.getExcludeTables().addAll(engExcludeTables);
-        }
-    }
-
-    private void compactColumnFilters() {
-        Collection<IncludeColumn> engIncludeColumns = engineering.getIncludeColumns();
-        Collection<ExcludeColumn> engExcludeColumns = engineering.getExcludeColumns();
-
-        engineering.setIncludeColumns(null);
-        engineering.setExcludeColumns(null);
-
-        for (Catalog catalog : engineering.getCatalogs()) {
-            Collection<IncludeColumn> catalogIncludeColumns = catalog.getIncludeColumns();
-            Collection<ExcludeColumn> catalogExcludeColumns = catalog.getExcludeColumns();
-
-            catalog.setIncludeColumns(null);
-            catalog.setExcludeColumns(null);
-
-            for (Schema schema : catalog.getSchemas()) {
-                Collection<IncludeColumn> schemaIncludeColumns = schema.getIncludeColumns();
-                Collection<ExcludeColumn> schemaExcludeColumns = schema.getExcludeColumns();
-
-                schema.setIncludeColumns(null);
-                schema.setExcludeColumns(null);
-
-                if (schema != null) {
-                    for (IncludeTable includeTable : schema.getIncludeTables()) {
-                        if (engIncludeColumns != null) {
-                            includeTable.getIncludeColumns().addAll(engIncludeColumns);
-                            includeTable.getIncludeColumns().addAll(catalogIncludeColumns);
-                            includeTable.getIncludeColumns().addAll(schemaIncludeColumns);
-                        }
-                        if (engExcludeColumns != null) {
-                            includeTable.getExcludeColumns().addAll(engExcludeColumns);
-                            includeTable.getExcludeColumns().addAll(catalogExcludeColumns);
-                            includeTable.getExcludeColumns().addAll(schemaExcludeColumns);
-                        }
-                    }
-                }
-            }
-
-            if (catalog.getIncludeTables() != null) {
-                for (IncludeTable includeTable : catalog.getIncludeTables()) {
-                    includeTable.getIncludeColumns().addAll(engIncludeColumns);
-                    includeTable.getIncludeColumns().addAll(catalogIncludeColumns);
-
-                    includeTable.getExcludeColumns().addAll(engExcludeColumns);
-                    includeTable.getExcludeColumns().addAll(catalogExcludeColumns);
-                }
-            }
-        }
-
-        for (Schema schema : engineering.getSchemas()) {
-            Collection<IncludeColumn> schemaIncludeColumns = schema.getIncludeColumns();
-            Collection<ExcludeColumn> schemaExcludeColumns = schema.getExcludeColumns();
-
-            schema.setIncludeColumns(null);
-            schema.setExcludeColumns(null);
-
-            for (IncludeTable includeTable : schema.getIncludeTables()) {
-                includeTable.getIncludeColumns().addAll(engIncludeColumns);
-                includeTable.getIncludeColumns().addAll(schemaIncludeColumns);
-
-                includeTable.getExcludeColumns().addAll(engExcludeColumns);
-                includeTable.getExcludeColumns().addAll(schemaExcludeColumns);
-            }
-        }
-
-        if (engineering.getIncludeTables() != null) {
-            for (IncludeTable includeTable : engineering.getIncludeTables()) {
-                includeTable.getIncludeColumns().addAll(engIncludeColumns);
-                includeTable.getExcludeColumns().addAll(engExcludeColumns);
-            }
-        }
-    }
-
-    private void addEmptyElements() {
-        if (engineering.getCatalogs().isEmpty()) {
-            engineering.addCatalog(new Catalog());
-        }
-
-        for (Catalog catalog : engineering.getCatalogs()) {
-            if (catalog.getSchemas().isEmpty()
-                    && engineering.getSchemas().isEmpty()) {
-                catalog.addSchema(new Schema());
-            }
-
-            for (Schema schema : catalog.getSchemas()) {
-                if (schema.getIncludeTables().isEmpty()
-                        && catalog.getIncludeTables().isEmpty()
-                        && engineering.getIncludeTables().isEmpty()) {
-
-                    schema.addIncludeTable(new IncludeTable());
-                }
-            }
-        }
-
-        if (engineering.getSchemas() == null) {
-            engineering.setSchemas(new LinkedList<Schema>());
-        }
-
-        for (Schema schema : engineering.getSchemas()) {
-            if (schema.getIncludeTables().isEmpty()
-                    && engineering.getIncludeTables().isEmpty()) {
-
-                schema.addIncludeTable(new IncludeTable());
-            }
-        }
-    }
-
-    public FiltersConfigBuilder add(LegacyFilterConfigBridge build) {
-        if (!isBlank(build.catalog())) {
-            engineering.addCatalog(new Catalog(build.catalog()));
-        }
-
-        if (!isBlank(build.schema())) {
-            engineering.addSchema(new Schema(build.schema()));
-        }
-
-        if (!isBlank(build.getIncludeTableFilters())) {
-            engineering.addIncludeTable(new IncludeTable(build.getIncludeTableFilters()));
-        }
-        if (!isBlank(build.getExcludeTableFilters())) {
-            engineering.addExcludeTable(new ExcludeTable(build.getExcludeTableFilters()));
-        }
-
-        if (!isBlank(build.getIncludeColumnFilters())) {
-            engineering.addIncludeColumn(new IncludeColumn(build.getIncludeColumnFilters()));
-        }
-        if (!isBlank(build.getExcludeColumnFilters())) {
-            engineering.addExcludeColumn(new ExcludeColumn(build.getExcludeColumnFilters()));
-        }
-
-        if (build.isLoadProcedures()) {
-            if (!isBlank(build.getIncludeProceduresFilters())) {
-                engineering.addIncludeProcedure(new IncludeProcedure(build.getIncludeProceduresFilters()));
-            }
-            if (!isBlank(build.getExcludeProceduresFilters())) {
-                engineering.addExcludeProcedure(new ExcludeProcedure(build.getExcludeProceduresFilters()));
-            }
-        }
-
-        return this;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/AbstractToDbToken.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/AbstractToDbToken.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/AbstractToDbToken.java
deleted file mode 100644
index 7430ba3..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/AbstractToDbToken.java
+++ /dev/null
@@ -1,126 +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.merge;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.log.JdbcEventLogger;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.validation.SimpleValidationFailure;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.List;
-
-/**
- * Common abstract superclass for all {@link MergerToken}s going from the model
- * to the database.
- */
-public abstract class AbstractToDbToken implements MergerToken, Comparable<MergerToken> {
-
-	private final String tokenName;
-
-	protected AbstractToDbToken(String tokenName) {
-		this.tokenName = tokenName;
-	}
-
-	@Override
-	public final String getTokenName() {
-		return tokenName;
-	}
-
-	@Override
-	public final MergeDirection getDirection() {
-		return MergeDirection.TO_DB;
-	}
-
-	@Override
-	public void execute(MergerContext mergerContext) {
-		for (String sql : createSql(mergerContext.getDataNode().getAdapter())) {
-			executeSql(mergerContext, sql);
-		}
-	}
-
-	protected void executeSql(MergerContext mergerContext, String sql) {
-		JdbcEventLogger logger = mergerContext.getDataNode().getJdbcEventLogger();
-		logger.log(sql);
-
-		try (Connection conn = mergerContext.getDataNode().getDataSource().getConnection();) {
-
-			try (Statement st = conn.createStatement();) {
-				st.execute(sql);
-			}
-		} catch (SQLException e) {
-			mergerContext.getValidationResult().addFailure(new SimpleValidationFailure(sql, e.getMessage()));
-			logger.logQueryError(e);
-		}
-	}
-
-	@Override
-	public String toString() {
-		return getTokenName() + ' ' + getTokenValue() + ' ' + getDirection();
-	}
-
-	public abstract List<String> createSql(DbAdapter adapter);
-
-	abstract static class Entity extends AbstractToDbToken {
-
-		private DbEntity entity;
-
-		public Entity(String tokenName, DbEntity entity) {
-			super(tokenName);
-			this.entity = entity;
-		}
-
-		public DbEntity getEntity() {
-			return entity;
-		}
-
-		public String getTokenValue() {
-			return getEntity().getName();
-		}
-
-		public int compareTo(MergerToken o) {
-			// default order as tokens are created
-			return 0;
-		}
-
-	}
-
-	abstract static class EntityAndColumn extends Entity {
-
-		private DbAttribute column;
-
-		public EntityAndColumn(String tokenName, DbEntity entity, DbAttribute column) {
-			super(tokenName, entity);
-			this.column = column;
-		}
-
-		public DbAttribute getColumn() {
-			return column;
-		}
-
-		@Override
-		public String getTokenValue() {
-			return getEntity().getName() + "." + getColumn().getName();
-		}
-
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/AbstractToModelToken.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/AbstractToModelToken.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/AbstractToModelToken.java
deleted file mode 100644
index 49b680b..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/AbstractToModelToken.java
+++ /dev/null
@@ -1,122 +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.merge;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-
-/**
- * Common abstract superclass for all {@link MergerToken}s going from the database to the
- * model.
- * 
- */
-public abstract class AbstractToModelToken implements MergerToken {
-
-    private final String tokenName;
-
-    protected AbstractToModelToken(String tokenName) {
-        this.tokenName = tokenName;
-    }
-
-    @Override
-    public final String getTokenName() {
-        return tokenName;
-    }
-
-    public final MergeDirection getDirection() {
-        return MergeDirection.TO_MODEL;
-    }
-
-    protected static void remove(ModelMergeDelegate mergerContext, DbRelationship rel, boolean reverse) {
-        if (rel == null) {
-            return;
-        }
-        if (reverse) {
-            remove(mergerContext, rel.getReverseRelationship(), false);
-        }
-
-        DbEntity dbEntity = rel.getSourceEntity();
-        for (ObjEntity objEntity : dbEntity.mappedObjEntities()) {
-            remove(mergerContext, objEntity.getRelationshipForDbRelationship(rel), true);
-        }
-        
-        rel.getSourceEntity().removeRelationship(rel.getName());
-        mergerContext.dbRelationshipRemoved(rel);
-    }
-
-    protected static void remove(ModelMergeDelegate mergerContext, ObjRelationship rel, boolean reverse) {
-        if (rel == null) {
-            return;
-        }
-        if (reverse) {
-            remove(mergerContext, rel.getReverseRelationship(), false);
-        }
-        rel.getSourceEntity().removeRelationship(rel.getName());
-        mergerContext.objRelationshipRemoved(rel);
-    }
-
-    @Override
-    public String toString() {
-        return getTokenName() + ' ' + getTokenValue() + ' ' + getDirection();
-    }
-    
-    abstract static class Entity extends AbstractToModelToken {
-        
-        private final DbEntity entity;
-
-        protected Entity(String tokenName, DbEntity entity) {
-            super(tokenName);
-            this.entity = entity;
-        }
-
-        public DbEntity getEntity() {
-            return entity;
-        }
-        
-        public String getTokenValue() {
-            return getEntity().getName();
-        }
-        
-    }
-    
-    abstract static class EntityAndColumn extends Entity {
-        
-        private final DbAttribute column;
-        
-        protected EntityAndColumn(String tokenName, DbEntity entity, DbAttribute column) {
-            super(tokenName, entity);
-            this.column = column;
-        }
-
-        public DbAttribute getColumn() {
-            return column;
-        }
-
-        @Override
-        public String getTokenValue() {
-            return getEntity().getName() + "." + getColumn().getName();
-        }
-        
-    }
-
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index 77b7486..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToDb.java
+++ /dev/null
@@ -1,64 +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.merge;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.JdbcAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-public class AddColumnToDb extends AbstractToDbToken.EntityAndColumn {
-
-    public AddColumnToDb(DbEntity entity, DbAttribute column) {
-        super("Add Column", entity, column);
-    }
-
-    /**
-     * append the part of the token before the actual column data type
-     */
-    protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-
-        sqlBuffer.append("ALTER TABLE ");
-        sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-        sqlBuffer.append(" ADD COLUMN ");
-        sqlBuffer.append(context.quotedName(getColumn()));
-        sqlBuffer.append(" ");
-    }
-
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        StringBuffer sqlBuffer = new StringBuffer();
-        QuotingStrategy context = adapter.getQuotingStrategy();
-        appendPrefix(sqlBuffer, context);
-
-        sqlBuffer.append(JdbcAdapter.getType(adapter, getColumn()));
-        sqlBuffer.append(JdbcAdapter.sizeAndPrecision(adapter, getColumn()));
-
-        return Collections.singletonList(sqlBuffer.toString());
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createDropColumnToModel(getEntity(), getColumn());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToModel.java
deleted file mode 100644
index 9e96928..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/AddColumnToModel.java
+++ /dev/null
@@ -1,55 +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.merge;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.util.EntityMergeSupport;
-
-/**
- * A {@link MergerToken} to add a {@link DbAttribute} to a {@link DbEntity}. The
- * {@link EntityMergeSupport} will be used to update the mapped {@link ObjEntity}
- * 
- */
-public class AddColumnToModel extends AbstractToModelToken.EntityAndColumn {
-
-    public AddColumnToModel(DbEntity entity, DbAttribute column) {
-        super("Add Column", entity, column);
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createDropColumnToDb(getEntity(), getColumn());
-    }
-
-    public void execute(MergerContext mergerContext) {
-        getEntity().addAttribute(getColumn());
-
-        // TODO: use EntityMergeSupport from DbImportConfiguration... otherwise we are ignoring a bunch of
-        // important settings
-
-        EntityMergeSupport entityMergeSupport =  new EntityMergeSupport(mergerContext.getDataMap());
-        for(ObjEntity e : getEntity().mappedObjEntities()) {
-            entityMergeSupport.synchronizeOnDbAttributeAdded(e, getColumn());
-        }
-
-        mergerContext.getModelMergeDelegate().dbAttributeAdded(getColumn());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/AddRelationshipToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/AddRelationshipToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/AddRelationshipToDb.java
deleted file mode 100644
index dbd65d8..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/AddRelationshipToDb.java
+++ /dev/null
@@ -1,86 +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.merge;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.access.DbGenerator;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-
-public class AddRelationshipToDb extends AbstractToDbToken.Entity {
-
-    private DbRelationship rel;
-
-    public AddRelationshipToDb(DbEntity entity, DbRelationship rel) {
-        super("Add foreign key", entity);
-        this.rel = rel;
-    }
-
-    /**
-     * @see DbGenerator#createConstraintsQueries(org.apache.cayenne.map.DbEntity)
-     */
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        // TODO: skip FK to a different DB
-
-        if (this.shouldGenerateFkConstraint()) {
-            String fksql = adapter.createFkConstraint(rel);
-            if (fksql != null) {
-                return Collections.singletonList(fksql);
-            }
-        }
-        return Collections.emptyList();
-    }
-
-    public boolean shouldGenerateFkConstraint() {
-        return !rel.isToMany()
-                && rel.isToPK() // TODO it is not necessary primary key it can be unique index
-                && !rel.isToDependentPK();
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createDropRelationshipToModel(getEntity(), rel);
-    }
-
-    @Override
-    public String getTokenValue() {
-        if (this.shouldGenerateFkConstraint()) {
-            return rel.getSourceEntity().getName() + "->" + rel.getTargetEntityName();
-        } else {
-            return "Skip. No sql representation.";
-        }
-    }
-    
-    public DbRelationship getRelationship() {
-        return rel;
-    }
-    
-    @Override
-    public int compareTo(MergerToken o) {
-        // add all AddRelationshipToDb to the end.
-        if (o instanceof AddRelationshipToDb) {
-            return super.compareTo(o);
-        }
-        return 1;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/AddRelationshipToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/AddRelationshipToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/AddRelationshipToModel.java
deleted file mode 100644
index 42d7329..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/AddRelationshipToModel.java
+++ /dev/null
@@ -1,95 +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.merge;
-
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.util.EntityMergeSupport;
-
-public class AddRelationshipToModel extends AbstractToModelToken.Entity {
-
-    public static final String COMMA_SEPARATOR = ", ";
-    public static final int COMMA_SEPARATOR_LENGTH = COMMA_SEPARATOR.length();
-    private DbRelationship rel;
-
-    public AddRelationshipToModel(DbEntity entity, DbRelationship rel) {
-        super("Add Relationship", entity);
-        this.rel = rel;
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createDropRelationshipToDb(getEntity(), rel);
-    }
-
-    public void execute(MergerContext mergerContext) {
-        getEntity().addRelationship(rel);
-        // TODO: add reverse relationship as well if it does not exist
-
-        // TODO: use EntityMergeSupport from DbImportConfiguration... otherwise we are ignoring a bunch of
-        // important settings
-
-        EntityMergeSupport entityMergeSupport =  new EntityMergeSupport(mergerContext.getDataMap());
-        for(ObjEntity e : getEntity().mappedObjEntities()) {
-            entityMergeSupport.synchronizeOnDbRelationshipAdded(e, rel);
-        }
-
-        mergerContext.getModelMergeDelegate().dbRelationshipAdded(rel);
-    }
-
-    @Override
-    public String getTokenValue() {
-        String attributes = "";
-        if (rel.getJoins().size() == 1) {
-            attributes = rel.getJoins().get(0).getTargetName();
-        } else {
-            for (DbJoin dbJoin : rel.getJoins()) {
-                attributes += dbJoin.getTargetName() + COMMA_SEPARATOR;
-            }
-
-            attributes = "{" + attributes.substring(0, attributes.length() - COMMA_SEPARATOR_LENGTH) + "}";
-        }
-
-        return rel.getName() + " " + rel.getSourceEntity().getName() + "->" + rel.getTargetEntityName() + "." + attributes;
-    }
-
-
-    public static String getTokenValue(DbRelationship rel) {
-        String attributes = "";
-        if (rel.getJoins().size() == 1) {
-            attributes = rel.getJoins().get(0).getTargetName();
-        } else {
-            for (DbJoin dbJoin : rel.getJoins()) {
-                attributes += dbJoin.getTargetName() + COMMA_SEPARATOR;
-            }
-
-            attributes = "{" + attributes.substring(0, attributes.length() - COMMA_SEPARATOR_LENGTH) + "}";
-        }
-
-        return rel.getName() + " " + rel.getSourceEntity().getName() + "->" + rel.getTargetEntityName() + "." + attributes;
-    }
-
-    public DbRelationship getRelationship() {
-        return rel;
-    }
-
-
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToDb.java
deleted file mode 100644
index 077ec74..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToDb.java
+++ /dev/null
@@ -1,65 +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.merge;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.validation.SimpleValidationFailure;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class CreateTableToDb extends AbstractToDbToken.Entity {
-
-    public CreateTableToDb(DbEntity entity) {
-        super("Create Table", entity);
-    }
-
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        List<String> sqls = new ArrayList<String>();
-        sqls.addAll(adapter.getPkGenerator().createAutoPkStatements(
-                Collections.singletonList(getEntity())));
-        sqls.add(adapter.createTable(getEntity()));
-        return sqls;
-    }
-
-    @Override
-    public void execute(MergerContext mergerContext) {
-        try {
-            DataNode node = mergerContext.getDataNode();
-            DbAdapter adapter = node.getAdapter();
-            adapter.getPkGenerator().createAutoPk(
-                    node,
-                    Collections.singletonList(getEntity()));
-            executeSql(mergerContext, adapter.createTable(getEntity()));
-        }
-        catch (Exception e) {
-            mergerContext.getValidationResult().addFailure(
-                    new SimpleValidationFailure(this, e.getMessage()));
-        }
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createDropTableToModel(getEntity());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index cdd6009..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/CreateTableToModel.java
+++ /dev/null
@@ -1,102 +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.merge;
-
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.naming.NameConverter;
-import org.apache.cayenne.util.EntityMergeSupport;
-
-/**
- * A {@link MergerToken} to add a {@link DbEntity} to a {@link DataMap}
- * 
- */
-public class CreateTableToModel extends AbstractToModelToken.Entity {
-
-    /**
-     * className if {@link ObjEntity} should be generated with a
-     *  special class name.
-     * Setting this to <code>null</code>, because by default class name should be generated 
-     */
-    private String objEntityClassName = null; //CayenneDataObject.class.getName();
-
-    public CreateTableToModel(DbEntity entity) {
-        super("Create Table", entity);
-    }
-
-    /**
-     * Set the {@link ObjEntity} className if {@link ObjEntity} should be generated with a
-     * special class name. Set to null if the {@link ObjEntity} should be created with a
-     * name based on {@link DataMap#getDefaultPackage()} and {@link ObjEntity#getName()}
-     * <p>
-     * The default value is <code>null</code>
-     */
-    public void setObjEntityClassName(String n) {
-        objEntityClassName = n;
-    }
-
-    public void execute(MergerContext mergerContext) {
-        DataMap map = mergerContext.getDataMap();
-        map.addDbEntity(getEntity());
-
-        // create a ObjEntity
-        String objEntityName = NameConverter.underscoredToJava(getEntity().getName(), true);
-        // this loop will terminate even if no valid name is found
-        // to prevent loader from looping forever (though such case is very unlikely)
-        String baseName = objEntityName;
-        for (int i = 1; i < 1000 && map.getObjEntity(objEntityName) != null; i++) {
-            objEntityName = baseName + i;
-        }
-
-        ObjEntity objEntity = new ObjEntity(objEntityName);
-        objEntity.setDbEntity(getEntity());
-
-        // try to find a class name for the ObjEntity
-        String className = objEntityClassName;
-        if (className == null) {
-            // we should generate a className based on the objEntityName
-            className = map.getNameWithDefaultPackage(objEntityName);
-        }
-
-        objEntity.setClassName(className);
-        objEntity.setSuperClassName(map.getDefaultSuperclass());
-        
-        if (map.isClientSupported()) {
-            objEntity.setClientClassName(map.getNameWithDefaultClientPackage(objEntity.getName()));
-            objEntity.setClientSuperClassName(map.getDefaultClientSuperclass());
-        }
-        
-        map.addObjEntity(objEntity);
-
-        // presumably there are no other ObjEntities pointing to this DbEntity, so syncing just this one...
-
-        // TODO: use EntityMergeSupport from DbImportConfiguration... otherwise we are ignoring a bunch of
-        // important settings
-        new EntityMergeSupport(map).synchronizeWithDbEntity(objEntity);
-
-        mergerContext.getModelMergeDelegate().dbEntityAdded(getEntity());
-        mergerContext.getModelMergeDelegate().objEntityAdded(objEntity);
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createDropTableToDb(getEntity());
-    }
-
-}


[13/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DefaultMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DefaultMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DefaultMergerTokenFactory.java
new file mode 100644
index 0000000..c2f96ce
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DefaultMergerTokenFactory.java
@@ -0,0 +1,181 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.AddColumnToModel;
+import org.apache.cayenne.dbsync.merge.AddRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.AddRelationshipToModel;
+import org.apache.cayenne.dbsync.merge.CreateTableToDb;
+import org.apache.cayenne.dbsync.merge.CreateTableToModel;
+import org.apache.cayenne.dbsync.merge.DropColumnToDb;
+import org.apache.cayenne.dbsync.merge.DropColumnToModel;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToModel;
+import org.apache.cayenne.dbsync.merge.DropTableToDb;
+import org.apache.cayenne.dbsync.merge.DropTableToModel;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToModel;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToModel;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToModel;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToDb;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToModel;
+import org.apache.cayenne.dbsync.merge.SetValueForNullToDb;
+import org.apache.cayenne.dbsync.merge.ValueForNullProvider;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.Collection;
+
+/**
+ * @since 4.0
+ */
+public class DefaultMergerTokenFactory implements MergerTokenFactory {
+
+    @Override
+    public MergerToken createCreateTableToModel(DbEntity entity) {
+        return new CreateTableToModel(entity);
+    }
+
+    @Override
+    public MergerToken createCreateTableToDb(DbEntity entity) {
+        return new CreateTableToDb(entity);
+    }
+
+    @Override
+    public MergerToken createDropTableToModel(DbEntity entity) {
+        return new DropTableToModel(entity);
+    }
+
+    @Override
+    public MergerToken createDropTableToDb(DbEntity entity) {
+        return new DropTableToDb(entity);
+    }
+
+    @Override
+    public MergerToken createAddColumnToModel(DbEntity entity, DbAttribute column) {
+        return new AddColumnToModel(entity, column);
+    }
+
+    @Override
+    public MergerToken createAddColumnToDb(DbEntity entity, DbAttribute column) {
+        return new AddColumnToDb(entity, column);
+    }
+
+    @Override
+    public MergerToken createDropColumnToModel(DbEntity entity, DbAttribute column) {
+        return new DropColumnToModel(entity, column);
+    }
+
+    @Override
+    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
+        return new DropColumnToDb(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetNotNullToModel(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToModel(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToModel(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToModel(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetValueForNullToDb(DbEntity entity, DbAttribute column, ValueForNullProvider valueForNullProvider) {
+        return new SetValueForNullToDb(entity, column, valueForNullProvider);
+    }
+
+    @Override
+    public MergerToken createSetColumnTypeToModel(
+            DbEntity entity,
+            DbAttribute columnOriginal,
+            DbAttribute columnNew) {
+        return new SetColumnTypeToModel(entity, columnOriginal, columnNew);
+    }
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            DbEntity entity,
+            DbAttribute columnOriginal,
+            DbAttribute columnNew) {
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew);
+    }
+
+    @Override
+    public MergerToken createAddRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        return new AddRelationshipToDb(entity, rel);
+    }
+
+    @Override
+    public MergerToken createAddRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        return new AddRelationshipToModel(entity, rel);
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        return new DropRelationshipToDb(entity, rel);
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        return new DropRelationshipToModel(entity, rel);
+    }
+
+    @Override
+    public MergerToken createSetPrimaryKeyToDb(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToDb(
+                entity,
+                primaryKeyOriginal,
+                primaryKeyNew,
+                detectedPrimaryKeyName);
+    }
+
+    @Override
+    public MergerToken createSetPrimaryKeyToModel(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToModel(
+                entity,
+                primaryKeyOriginal,
+                primaryKeyNew,
+                detectedPrimaryKeyName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DerbyMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DerbyMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DerbyMergerTokenFactory.java
new file mode 100644
index 0000000..398d5cc
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DerbyMergerTokenFactory.java
@@ -0,0 +1,85 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+public class DerbyMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://db.apache.org/derby/manuals/reference/sqlj26.html
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" SET DATA TYPE ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
+                        + " ALTER COLUMN " + context.quotedName(getColumn()) + " NOT NULL");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
+                        + " ALTER COLUMN " + context.quotedName(getColumn()) + " NULL");
+            }
+
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/FirebirdMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/FirebirdMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/FirebirdMergerTokenFactory.java
new file mode 100644
index 0000000..4368977
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/FirebirdMergerTokenFactory.java
@@ -0,0 +1,91 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.DropColumnToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+public class FirebirdMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
+        return new DropColumnToDb(entity, column) {
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy quoting = adapter.getQuotingStrategy();
+                return Collections.singletonList("ALTER TABLE " + quoting.quotedFullyQualifiedName(getEntity())
+                        + " DROP " + quoting.quotedName(getColumn()));
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                String entityName = context.quotedFullyQualifiedName(getEntity()) ;
+                String columnName = context.quotedName(getColumn());
+                // Firebird doesn't support ALTER TABLE table_name ALTER column_name SET NOT NULL
+                // but this might be achived by modyfication of system tables 
+                return Collections.singletonList(String.format("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = 1 "+ 
+                "WHERE RDB$FIELD_NAME = '%s' AND RDB$RELATION_NAME = '%s'", columnName, entityName));
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                String entityName = context.quotedFullyQualifiedName(getEntity()) ;
+                String columnName = context.quotedName(getColumn()); 
+                // Firebird doesn't support ALTER TABLE table_name ALTER column_name DROP NOT NULL
+                // but this might be achived by modyfication system tables 
+                return Collections.singletonList(String.format("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = NULL "+
+                " WHERE RDB$FIELD_NAME = '%s' AND RDB$RELATION_NAME = '%s'", columnName, entityName));
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createAddColumnToDb(DbEntity entity, DbAttribute column) {
+        return new AddColumnToDb(entity, column) {
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ADD ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/H2MergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/H2MergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/H2MergerTokenFactory.java
new file mode 100644
index 0000000..8acafc3
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/H2MergerTokenFactory.java
@@ -0,0 +1,82 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @since 3.0
+ */
+public class H2MergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                return Collections.singletonList("ALTER TABLE " + getEntity().getFullyQualifiedName()
+                        + " ALTER COLUMN " + getColumn().getName() + " SET NULL");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetPrimaryKeyToDb(DbEntity entity, Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToDb(entity, primaryKeyOriginal, primaryKeyNew, detectedPrimaryKeyName) {
+
+            @Override
+            protected void appendDropOriginalPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
+                sqls.add("ALTER TABLE " + adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity())
+                        + " DROP PRIMARY KEY");
+            }
+
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/HSQLMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/HSQLMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/HSQLMergerTokenFactory.java
new file mode 100644
index 0000000..15cfa18
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/HSQLMergerTokenFactory.java
@@ -0,0 +1,82 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class HSQLMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
+                        + " ALTER COLUMN " + context.quotedName(getColumn()) + " NULL");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetPrimaryKeyToDb(DbEntity entity, Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToDb(entity, primaryKeyOriginal, primaryKeyNew, detectedPrimaryKeyName) {
+
+            @Override
+            protected void appendDropOriginalPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
+                sqls.add("ALTER TABLE " + adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity())
+                        + " DROP PRIMARY KEY");
+            }
+
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
new file mode 100644
index 0000000..19d2860
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
@@ -0,0 +1,224 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.DropColumnToDb;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.Collections;
+import java.util.List;
+
+public class IngresMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER COLUMN ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
+        return new DropColumnToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuilder buf = new StringBuilder();
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                buf.append("ALTER TABLE ");
+                buf.append(context.quotedFullyQualifiedName(getEntity()));
+                buf.append(" DROP COLUMN ");
+                buf.append(context.quotedName(getColumn()));
+                buf.append(" RESTRICT ");
+
+                return Collections.singletonList(buf.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createAddRelationshipToDb(DbEntity entity, final DbRelationship rel) {
+        return new AddRelationshipToDb(entity, rel) {
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                if (!rel.isToMany() && rel.isToPK() && !rel.isToDependentPK()) {
+
+                    DbEntity source = (DbEntity) rel.getSourceEntity();
+                    QuotingStrategy context = adapter.getQuotingStrategy();
+                    StringBuilder buf = new StringBuilder();
+                    StringBuilder refBuf = new StringBuilder();
+
+                    buf.append("ALTER TABLE ");
+                    buf.append(context.quotedFullyQualifiedName(source));
+
+                    // requires the ADD CONSTRAINT statement
+                    buf.append(" ADD CONSTRAINT ");
+                    String name = "U_" + rel.getSourceEntity().getName() + "_"
+                            + (long) (System.currentTimeMillis() / (Math.random() * 100000));
+
+                    buf.append(context.quotedIdentifier(rel.getSourceEntity(), name));
+                    buf.append(" FOREIGN KEY (");
+
+                    boolean first = true;
+                    for (DbJoin join : rel.getJoins()) {
+                        if (!first) {
+                            buf.append(", ");
+                            refBuf.append(", ");
+                        } else
+                            first = false;
+
+                        buf.append(context.quotedSourceName(join));
+                        refBuf.append(context.quotedTargetName(join));
+                    }
+
+                    buf.append(") REFERENCES ");
+                    buf.append(context.quotedFullyQualifiedName((DbEntity) rel.getTargetEntity()));
+                    buf.append(" (");
+                    buf.append(refBuf.toString());
+                    buf.append(')');
+
+                    // also make sure we delete dependent FKs
+                    buf.append(" ON DELETE CASCADE");
+
+                    String fksql = buf.toString();
+
+                    if (fksql != null) {
+                        return Collections.singletonList(fksql);
+                    }
+                }
+
+                return Collections.emptyList();
+
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                /*
+                 * TODO: we generate this query as in ingres db documentation,
+                 * but unfortunately ingres don't support it
+                 */
+
+                StringBuilder sqlBuffer = new StringBuilder();
+
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(getEntity().getFullyQualifiedName());
+                sqlBuffer.append(" ALTER COLUMN ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+                sqlBuffer.append(adapter.externalTypesForJdbcType(getColumn().getType())[0]);
+
+                if (adapter.typeSupportsLength(getColumn().getType()) && getColumn().getMaxLength() > 0) {
+                    sqlBuffer.append("(");
+                    sqlBuffer.append(getColumn().getMaxLength());
+                    sqlBuffer.append(")");
+                }
+
+                sqlBuffer.append(" NOT NULL");
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuilder sqlBuffer = new StringBuilder();
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ALTER COLUMN ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+                sqlBuffer.append(adapter.externalTypesForJdbcType(getColumn().getType())[0]);
+
+                if (adapter.typeSupportsLength(getColumn().getType()) && getColumn().getMaxLength() > 0) {
+                    sqlBuffer.append("(");
+                    sqlBuffer.append(getColumn().getMaxLength());
+                    sqlBuffer.append(")");
+                }
+
+                sqlBuffer.append(" WITH NULL");
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(final DbEntity entity, DbRelationship rel) {
+
+        return new DropRelationshipToDb(entity, rel) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                String fkName = getFkName();
+
+                if (fkName == null) {
+                    return Collections.emptyList();
+                }
+                
+                StringBuilder buf = new StringBuilder();
+                buf.append("ALTER TABLE ");
+                buf.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                buf.append(" DROP CONSTRAINT ");
+                buf.append(fkName);
+                buf.append(" CASCADE ");
+
+                return Collections.singletonList(buf.toString());
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactory.java
new file mode 100644
index 0000000..46b6ef3
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactory.java
@@ -0,0 +1,88 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.ValueForNullProvider;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.Collection;
+
+public interface MergerTokenFactory {
+
+    MergerToken createCreateTableToModel(DbEntity entity);
+
+    MergerToken createCreateTableToDb(DbEntity entity);
+
+    MergerToken createDropTableToModel(DbEntity entity);
+
+    MergerToken createDropTableToDb(DbEntity entity);
+
+    MergerToken createAddColumnToModel(DbEntity entity, DbAttribute column);
+
+    MergerToken createAddColumnToDb(DbEntity entity, DbAttribute column);
+
+    MergerToken createDropColumnToModel(DbEntity entity, DbAttribute column);
+
+    MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetNotNullToModel(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetAllowNullToModel(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetValueForNullToDb(DbEntity entity,
+                                          DbAttribute column,
+                                          ValueForNullProvider valueForNullProvider);
+
+    MergerToken createSetColumnTypeToModel(
+            DbEntity entity,
+            DbAttribute columnOriginal,
+            DbAttribute columnNew);
+
+    MergerToken createSetColumnTypeToDb(
+            DbEntity entity,
+            DbAttribute columnOriginal,
+            DbAttribute columnNew);
+
+    MergerToken createAddRelationshipToDb(DbEntity entity, DbRelationship rel);
+
+    MergerToken createAddRelationshipToModel(DbEntity entity, DbRelationship rel);
+
+    MergerToken createDropRelationshipToDb(DbEntity entity, DbRelationship rel);
+
+    MergerToken createDropRelationshipToModel(DbEntity entity, DbRelationship rel);
+
+    MergerToken createSetPrimaryKeyToDb(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName);
+
+    MergerToken createSetPrimaryKeyToModel(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactoryProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactoryProvider.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactoryProvider.java
new file mode 100644
index 0000000..8d5b573
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactoryProvider.java
@@ -0,0 +1,36 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.PerAdapterProvider;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
+import org.apache.cayenne.di.Inject;
+
+import java.util.Map;
+
+/**
+ * @since 4.0
+ */
+public class MergerTokenFactoryProvider extends PerAdapterProvider<MergerTokenFactory> {
+
+    public MergerTokenFactoryProvider(@Inject(CayenneDbSyncModule.MERGER_FACTORIES_MAP) Map<String,
+            MergerTokenFactory> perAdapterValues, @Inject MergerTokenFactory defaultValue) {
+        super(perAdapterValues, defaultValue);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MySQLMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MySQLMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MySQLMergerTokenFactory.java
new file mode 100644
index 0000000..2193446
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MySQLMergerTokenFactory.java
@@ -0,0 +1,156 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class MySQLMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetNotNullToDb(
+            final DbEntity entity,
+            final DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" CHANGE ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(
+            final DbEntity entity,
+            final DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" CHANGE ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://dev.mysql.com/tech-resources/articles/mysql-cluster-50.html
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" MODIFY ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(
+            final DbEntity entity,
+            DbRelationship rel) {
+
+        return new DropRelationshipToDb(entity, rel) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                String fkName = getFkName();
+
+                if (fkName == null) {
+                    return Collections.emptyList();
+                }
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                // http://dev.mysql.com/tech-resources/articles/mysql-cluster-50.html
+                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(entity) + " DROP FOREIGN KEY " + fkName);
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetPrimaryKeyToDb(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToDb(
+                entity,
+                primaryKeyOriginal,
+                primaryKeyNew,
+                detectedPrimaryKeyName) {
+
+            @Override
+            protected void appendDropOriginalPrimaryKeySQL(
+                    DbAdapter adapter,
+                    List<String> sqls) {
+                sqls.add("ALTER TABLE "
+                        + adapter.getQuotingStrategy()
+                                .quotedFullyQualifiedName(getEntity())
+                        + " DROP PRIMARY KEY");
+            }
+
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OpenBaseMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OpenBaseMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OpenBaseMergerTokenFactory.java
new file mode 100644
index 0000000..7235f6b
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OpenBaseMergerTokenFactory.java
@@ -0,0 +1,143 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.CreateTableToDb;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class OpenBaseMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createCreateTableToDb(DbEntity entity) {
+        return new CreateTableToDb(entity) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                List<String> sqls = new ArrayList<String>();
+                // create table first as OpenBase adapter created primary key in its
+                // getPkGenerator().createAutoPkStatements
+                sqls.add(adapter.createTable(getEntity()));
+                sqls.addAll(adapter.getPkGenerator().createAutoPkStatements(
+                        Collections.singletonList(getEntity())));
+                return sqls;
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(
+            final DbEntity entity,
+            final DbRelationship rel) {
+        return new DropRelationshipToDb(entity, rel) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+
+                // FK_NAME form jdbc metadata seem to be wrong. It contain a column name
+                // and not the 'relationshipName'
+                // TODO: tell openbase developer mail list
+
+                DbEntity source = getEntity();
+                DbEntity dest = rel.getTargetEntity();
+
+                // only use the first. See adapter
+                // TODO: can we be sure this is the first and same as used by the adapter?
+                DbJoin join = rel.getJoins().get(0);
+
+                // see comment in adapter for why source and dest is switched around..
+
+                return Collections.singletonList("delete from _SYS_RELATIONSHIP where "
+                        + " source_table = '" + dest.getFullyQualifiedName() + "'"
+                        + " and source_column = '" + join.getTargetName() + "'"
+                        + " and dest_table = '" + source.getFullyQualifiedName() + "'"
+                        + " and dest_column = '" + join.getSourceName() + "'");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            final DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                List<String> sqls = new ArrayList<String>();
+
+                if (columnOriginal.getMaxLength() != columnNew.getMaxLength()) {
+                    sqls.add("ALTER TABLE "
+                            + entity.getFullyQualifiedName()
+                            + " COLUMN "
+                            + columnNew.getName()
+                            + " SET LENGTH "
+                            + columnNew.getMaxLength());
+                }
+
+                return sqls;
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                return Collections.singletonList("ALTER TABLE " + getEntity().getFullyQualifiedName()
+                        + " COLUMN " + getColumn().getName() + " SET NOT NULL");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                return Collections.singletonList("ALTER TABLE " + getEntity().getFullyQualifiedName()
+                        + " COLUMN " + getColumn().getName() + " SET NULL");
+            }
+
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OracleMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OracleMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OracleMergerTokenFactory.java
new file mode 100644
index 0000000..2c4032b
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OracleMergerTokenFactory.java
@@ -0,0 +1,111 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+
+public class OracleMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createAddColumnToDb(final DbEntity entity, final DbAttribute column) {
+        return new AddColumnToDb(entity, column) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ADD ");
+                sqlBuffer.append(context.quotedName(column));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" MODIFY ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, final DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" MODIFY ");
+
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, final DbAttribute column) {
+
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" MODIFY ");
+
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/PostgresMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/PostgresMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/PostgresMergerTokenFactory.java
new file mode 100644
index 0000000..935ecfb
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/PostgresMergerTokenFactory.java
@@ -0,0 +1,49 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+
+public class PostgresMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy quotingStrategy) {
+                // http://www.postgresql.org/docs/8.2/static/sql-altertable.html
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(quotingStrategy.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER ");
+                sqlBuffer.append(quotingStrategy.quotedName(columnNew));
+                sqlBuffer.append(" TYPE ");
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SQLServerMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SQLServerMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SQLServerMergerTokenFactory.java
new file mode 100644
index 0000000..768b957
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SQLServerMergerTokenFactory.java
@@ -0,0 +1,112 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+public class SQLServerMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://msdn2.microsoft.com/en-us/library/ms190273.aspx
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER COLUMN ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createAddColumnToDb(final DbEntity entity, final DbAttribute column) {
+        return new AddColumnToDb(entity, column) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://msdn2.microsoft.com/en-us/library/ms190273.aspx
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ADD ");
+                sqlBuffer.append(context.quotedName(column));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, final DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ALTER COLUMN ");
+
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, final DbAttribute column) {
+
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ALTER COLUMN ");
+
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SybaseMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SybaseMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SybaseMergerTokenFactory.java
new file mode 100644
index 0000000..f295305
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SybaseMergerTokenFactory.java
@@ -0,0 +1,166 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.DropColumnToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @since 3.0
+ */
+public class SybaseMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createAddColumnToDb(DbEntity entity, final DbAttribute column) {
+        return new AddColumnToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                StringBuffer sqlBuffer = new StringBuffer();
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ADD ");
+                boolean magnatory = column.isMandatory();
+                column.setMandatory(false);
+                adapter.createTableAppendColumn(sqlBuffer, column);
+                if(magnatory){
+                    column.setMandatory(magnatory);
+                }
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
+        return new DropColumnToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuilder sqlBuffer = new StringBuilder();
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" DROP ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                StringBuffer sqlBuffer = createStringQuery(
+                        adapter,
+                        getEntity(),
+                        getColumn());
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = createStringQuery(
+                        adapter,
+                        getEntity(),
+                        getColumn());
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+        };
+    }
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://dev.mysql.com/tech-resources/articles/mysql-cluster-50.html
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" MODIFY ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+
+        };
+    }
+
+    private static StringBuffer createStringQuery(
+            DbAdapter adapter,
+            DbEntity entity,
+            DbAttribute column) {
+        StringBuffer sqlBuffer = new StringBuffer();
+        QuotingStrategy context = adapter.getQuotingStrategy();
+        sqlBuffer.append("ALTER TABLE ");
+        sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+        sqlBuffer.append(" MODIFY ");
+        adapter.createTableAppendColumn(sqlBuffer, column);
+
+        return sqlBuffer;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesBaseLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesBaseLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesBaseLoader.java
new file mode 100644
index 0000000..2bb55c0
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesBaseLoader.java
@@ -0,0 +1,107 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Set;
+
+/**
+* @since 4.0.
+*/
+public abstract class DbAttributesBaseLoader implements DbAttributesLoader {
+    private final String catalog;
+    private final String schema;
+
+    private final DatabaseMetaData metaData;
+    private final DbAdapter adapter;
+
+    public DbAttributesBaseLoader(String catalog, String schema, DatabaseMetaData metaData, DbAdapter adapter) {
+        this.catalog = catalog;
+        this.schema = schema;
+        this.metaData = metaData;
+        this.adapter = adapter;
+    }
+
+    protected DbAttribute loadDbAttribute(Set<String> columns, ResultSet rs) throws SQLException {
+
+        // gets attribute's (column's) information
+        int columnType = rs.getInt("DATA_TYPE");
+
+        // ignore precision of non-decimal columns
+        int decimalDigits = -1;
+        if (TypesMapping.isDecimal(columnType)) {
+            decimalDigits = rs.getInt("DECIMAL_DIGITS");
+            if (rs.wasNull()) {
+                decimalDigits = -1;
+            }
+        }
+
+        // create attribute delegating this task to adapter
+        DbAttribute attr = adapter.buildAttribute(
+                rs.getString("COLUMN_NAME"),
+                rs.getString("TYPE_NAME"),
+                columnType,
+                rs.getInt("COLUMN_SIZE"),
+                decimalDigits,
+                rs.getBoolean("NULLABLE"));
+
+        if (columns.contains("IS_AUTOINCREMENT")) {
+            String autoIncrement = rs.getString("IS_AUTOINCREMENT");
+            if ("YES".equals(autoIncrement)) {
+                attr.setGenerated(true);
+            }
+        }
+        return attr;
+    }
+
+    @Override
+    public void loadDbAttributes(DbEntity entity) {
+        for (DbAttribute attr : loadDbAttributes(entity.getName())) {
+            attr.setEntity(entity);
+
+            // override existing attributes if it comes again
+            if (entity.getAttribute(attr.getName()) != null) {
+                entity.removeAttribute(attr.getName());
+            }
+            entity.addAttribute(attr);
+        }
+    }
+
+    protected abstract List<DbAttribute> loadDbAttributes(String tableName);
+
+    protected String getCatalog() {
+        return catalog;
+    }
+
+    protected String getSchema() {
+        return schema;
+    }
+
+    protected DatabaseMetaData getMetaData() {
+        return metaData;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesLoader.java
new file mode 100644
index 0000000..6658cdc
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesLoader.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Interface responsible for attributes loading. Several options possible here
+ *  1) load attributes for each table separately
+ *  2) load attributes for schema and group it by table names
+ *
+ *  here is a trade of between count of queries and amount af calculation.
+ *
+ *
+ * @since 4.0
+ */
+public interface DbAttributesLoader {
+
+    // TODO use instant field for logging
+    Log LOGGER = LogFactory.getLog(DbTableLoader.class);
+
+    void loadDbAttributes(DbEntity entity);
+
+}
+

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesPerSchemaLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesPerSchemaLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesPerSchemaLoader.java
new file mode 100644
index 0000000..83ce60b
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesPerSchemaLoader.java
@@ -0,0 +1,130 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+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 org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * Load all attributes for schema and return it for each table
+ * */
+public class DbAttributesPerSchemaLoader extends DbAttributesBaseLoader {
+
+	private final TableFilter filter;
+
+	private Map<String, List<DbAttribute>> attributes;
+
+	public DbAttributesPerSchemaLoader(String catalog, String schema, DatabaseMetaData metaData, DbAdapter adapter,
+			TableFilter filter) {
+		super(catalog, schema, metaData, adapter);
+
+		this.filter = filter;
+	}
+
+	private Map<String, List<DbAttribute>> loadDbAttributes() throws SQLException {
+		Map<String, List<DbAttribute>> attributes = new HashMap<>();
+
+		try (ResultSet rs = getMetaData().getColumns(getCatalog(), getSchema(), "%", "%");) {
+			Set<String> columns = new HashSet<String>();
+
+			while (rs.next()) {
+				if (columns.isEmpty()) {
+					ResultSetMetaData rsMetaData = rs.getMetaData();
+					for (int i = 1; i <= rsMetaData.getColumnCount(); i++) {
+						columns.add(rsMetaData.getColumnLabel(i));
+					}
+				}
+
+				// for a reason not quiet apparent to me, Oracle sometimes
+				// 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");
+				String columnName = rs.getString("COLUMN_NAME");
+
+				PatternFilter columnFilter = filter.isIncludeTable(tableName);
+				/*
+				 * Here is possible optimization if filter will contain
+				 * map<tableName, columnFilter> we can replace it after tables
+				 * loading since already done pattern matching once and exactly
+				 * know all tables that we want to process
+				 */
+				if (columnFilter == null || !columnFilter.isInclude(columnName)) {
+					if (LOGGER.isDebugEnabled()) {
+						LOGGER.debug("Skip column '" + tableName + "." + columnName + "' (Path: " + getCatalog() + "/"
+								+ getSchema() + "; Filter: " + columnFilter + ")");
+					}
+					continue;
+				}
+
+				List<DbAttribute> attrs = attributes.get(tableName);
+				if (attrs == null) {
+					attrs = new LinkedList<DbAttribute>();
+
+					attributes.put(tableName, attrs);
+				}
+
+				attrs.add(loadDbAttribute(columns, rs));
+			}
+		}
+
+		return attributes;
+	}
+
+	@Override
+	protected List<DbAttribute> loadDbAttributes(String tableName) {
+		Map<String, List<DbAttribute>> attributes = getAttributes();
+		if (attributes != null) {
+			List<DbAttribute> dbAttributes = attributes.get(tableName);
+			if (dbAttributes != null) {
+				return dbAttributes;
+			}
+		}
+
+		return new LinkedList<DbAttribute>();
+	}
+
+	public Map<String, List<DbAttribute>> getAttributes() {
+		if (attributes == null) {
+			try {
+				attributes = loadDbAttributes();
+			} catch (SQLException e) {
+				LOGGER.error(e);
+				attributes = new HashMap<>();
+			}
+		}
+		return attributes;
+	}
+}


[08/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoaderDelegate.java b/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoaderDelegate.java
deleted file mode 100644
index ec31e10..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoaderDelegate.java
+++ /dev/null
@@ -1,58 +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 org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjEntity;
-
-/**
- * DbLoaderDelegate defines API that allows to control the behavior of DbLoader
- * during the database reverse-engineering. Delegate is also notified of the
- * progress of reverse-engineering.
- */
-public interface DbLoaderDelegate {
-
-    void dbEntityAdded(DbEntity entity);
-
-    void dbEntityRemoved(DbEntity entity);
-
-    /**
-     * Called before relationship loading for db-entity
-     * @param entity
-     *
-     * @return true in case you want process relationships for this entity
-     *         false otherwise
-     */
-    boolean dbRelationship(DbEntity entity);
-
-    /**
-     * Called before relationship will be added into db-entity but after it was loaded from db
-     * @param entity
-     *
-     * @return true in case you want add this relationship into entity
-     *         false otherwise
-     */
-    boolean dbRelationshipLoaded(DbEntity entity, DbRelationship relationship);
-
-    void objEntityAdded(ObjEntity entity);
-
-    void objEntityRemoved(ObjEntity entity);
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesBaseLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesBaseLoader.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesBaseLoader.java
deleted file mode 100644
index 57e35ac..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesBaseLoader.java
+++ /dev/null
@@ -1,107 +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.loader;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.List;
-import java.util.Set;
-
-/**
-* @since 4.0.
-*/
-public abstract class DbAttributesBaseLoader implements DbAttributesLoader {
-    private final String catalog;
-    private final String schema;
-
-    private final DatabaseMetaData metaData;
-    private final DbAdapter adapter;
-
-    public DbAttributesBaseLoader(String catalog, String schema, DatabaseMetaData metaData, DbAdapter adapter) {
-        this.catalog = catalog;
-        this.schema = schema;
-        this.metaData = metaData;
-        this.adapter = adapter;
-    }
-
-    protected DbAttribute loadDbAttribute(Set<String> columns, ResultSet rs) throws SQLException {
-
-        // gets attribute's (column's) information
-        int columnType = rs.getInt("DATA_TYPE");
-
-        // ignore precision of non-decimal columns
-        int decimalDigits = -1;
-        if (TypesMapping.isDecimal(columnType)) {
-            decimalDigits = rs.getInt("DECIMAL_DIGITS");
-            if (rs.wasNull()) {
-                decimalDigits = -1;
-            }
-        }
-
-        // create attribute delegating this task to adapter
-        DbAttribute attr = adapter.buildAttribute(
-                rs.getString("COLUMN_NAME"),
-                rs.getString("TYPE_NAME"),
-                columnType,
-                rs.getInt("COLUMN_SIZE"),
-                decimalDigits,
-                rs.getBoolean("NULLABLE"));
-
-        if (columns.contains("IS_AUTOINCREMENT")) {
-            String autoIncrement = rs.getString("IS_AUTOINCREMENT");
-            if ("YES".equals(autoIncrement)) {
-                attr.setGenerated(true);
-            }
-        }
-        return attr;
-    }
-
-    @Override
-    public void loadDbAttributes(DbEntity entity) {
-        for (DbAttribute attr : loadDbAttributes(entity.getName())) {
-            attr.setEntity(entity);
-
-            // override existing attributes if it comes again
-            if (entity.getAttribute(attr.getName()) != null) {
-                entity.removeAttribute(attr.getName());
-            }
-            entity.addAttribute(attr);
-        }
-    }
-
-    protected abstract List<DbAttribute> loadDbAttributes(String tableName);
-
-    protected String getCatalog() {
-        return catalog;
-    }
-
-    protected String getSchema() {
-        return schema;
-    }
-
-    protected DatabaseMetaData getMetaData() {
-        return metaData;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesLoader.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesLoader.java
deleted file mode 100644
index e4113b4..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesLoader.java
+++ /dev/null
@@ -1,43 +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.loader;
-
-import org.apache.cayenne.map.DbEntity;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * Interface responsible for attributes loading. Several options possible here
- *  1) load attributes for each table separately
- *  2) load attributes for schema and group it by table names
- *
- *  here is a trade of between count of queries and amount af calculation.
- *
- *
- * @since 4.0
- */
-public interface DbAttributesLoader {
-
-    // TODO use instant field for logging
-    Log LOGGER = LogFactory.getLog(DbTableLoader.class);
-
-    void loadDbAttributes(DbEntity entity);
-
-}
-

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesPerSchemaLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesPerSchemaLoader.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesPerSchemaLoader.java
deleted file mode 100644
index a7871ad..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbAttributesPerSchemaLoader.java
+++ /dev/null
@@ -1,130 +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.loader;
-
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-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 org.apache.cayenne.access.loader.filters.PatternFilter;
-import org.apache.cayenne.access.loader.filters.TableFilter;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbAttribute;
-
-/**
- * Load all attributes for schema and return it for each table
- * */
-public class DbAttributesPerSchemaLoader extends DbAttributesBaseLoader {
-
-	private final TableFilter filter;
-
-	private Map<String, List<DbAttribute>> attributes;
-
-	public DbAttributesPerSchemaLoader(String catalog, String schema, DatabaseMetaData metaData, DbAdapter adapter,
-			TableFilter filter) {
-		super(catalog, schema, metaData, adapter);
-
-		this.filter = filter;
-	}
-
-	private Map<String, List<DbAttribute>> loadDbAttributes() throws SQLException {
-		Map<String, List<DbAttribute>> attributes = new HashMap<>();
-
-		try (ResultSet rs = getMetaData().getColumns(getCatalog(), getSchema(), "%", "%");) {
-			Set<String> columns = new HashSet<String>();
-
-			while (rs.next()) {
-				if (columns.isEmpty()) {
-					ResultSetMetaData rsMetaData = rs.getMetaData();
-					for (int i = 1; i <= rsMetaData.getColumnCount(); i++) {
-						columns.add(rsMetaData.getColumnLabel(i));
-					}
-				}
-
-				// for a reason not quiet apparent to me, Oracle sometimes
-				// 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");
-				String columnName = rs.getString("COLUMN_NAME");
-
-				PatternFilter columnFilter = filter.isIncludeTable(tableName);
-				/*
-				 * Here is possible optimization if filter will contain
-				 * map<tableName, columnFilter> we can replace it after tables
-				 * loading since already done pattern matching once and exactly
-				 * know all tables that we want to process
-				 */
-				if (columnFilter == null || !columnFilter.isInclude(columnName)) {
-					if (LOGGER.isDebugEnabled()) {
-						LOGGER.debug("Skip column '" + tableName + "." + columnName + "' (Path: " + getCatalog() + "/"
-								+ getSchema() + "; Filter: " + columnFilter + ")");
-					}
-					continue;
-				}
-
-				List<DbAttribute> attrs = attributes.get(tableName);
-				if (attrs == null) {
-					attrs = new LinkedList<DbAttribute>();
-
-					attributes.put(tableName, attrs);
-				}
-
-				attrs.add(loadDbAttribute(columns, rs));
-			}
-		}
-
-		return attributes;
-	}
-
-	@Override
-	protected List<DbAttribute> loadDbAttributes(String tableName) {
-		Map<String, List<DbAttribute>> attributes = getAttributes();
-		if (attributes != null) {
-			List<DbAttribute> dbAttributes = attributes.get(tableName);
-			if (dbAttributes != null) {
-				return dbAttributes;
-			}
-		}
-
-		return new LinkedList<DbAttribute>();
-	}
-
-	public Map<String, List<DbAttribute>> getAttributes() {
-		if (attributes == null) {
-			try {
-				attributes = loadDbAttributes();
-			} catch (SQLException e) {
-				LOGGER.error(e);
-				attributes = new HashMap<>();
-			}
-		}
-		return attributes;
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbLoaderConfiguration.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbLoaderConfiguration.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbLoaderConfiguration.java
deleted file mode 100644
index 59365cc..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbLoaderConfiguration.java
+++ /dev/null
@@ -1,150 +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.loader;
-
-import org.apache.cayenne.access.loader.filters.TableFilter;
-import org.apache.cayenne.access.loader.filters.FiltersConfig;
-import org.apache.cayenne.access.loader.filters.PatternFilter;
-
-/**
- * @since 4.0
- */
-public class DbLoaderConfiguration {
-
-    /**
-     * Returns a name of a generic class that should be used for all
-     * ObjEntities. The most common generic class is
-     * {@link org.apache.cayenne.CayenneDataObject}. If generic class name is
-     * null (which is the default), DbLoader will assign each entity a unique
-     * class name derived from the table name.
-     *
-     */
-    private String genericClassName;
-
-/*
-    // TODO: Andrus, 10/29/2005 - this type of filtering should be delegated to adapter
-       TODO by default should skip name.startsWith("BIN$")
-
-    private NameFilter tableFilter = NamePatternMatcher.build(null, null, "BIN$");
-
-    private NameFilter columnFilter;
-
-    private NameFilter proceduresFilter = new NameFilter() {
-        private final Collection<String> excludedProcedures = Arrays.asList(
-                "auto_pk_for_table",
-                "auto_pk_for_table;1" // the last name is some Mac OS X Sybase artifact
-        );
-
-        @Override
-        public boolean isIncluded(String string) {
-            return !excludedProcedures.contains(string);
-        }
-    };
-*/
-
-
-    /**
-     * Java class implementing org.apache.cayenne.map.naming.NamingStrategy.
-     * This is used to specify how ObjEntities will be mapped from the imported
-     * DB schema.
-     */
-    private String namingStrategy;
-
-    private Boolean skipRelationshipsLoading;
-
-    private Boolean skipPrimaryKeyLoading;
-
-    private String[] tableTypes;
-
-    private FiltersConfig filtersConfig;
-
-    public String getGenericClassName() {
-        return genericClassName;
-    }
-
-    public void setGenericClassName(String genericClassName) {
-        this.genericClassName = genericClassName;
-    }
-
-    public String[] getTableTypes() {
-        return tableTypes;
-    }
-
-    public void setTableTypes(String[] tableTypes) {
-        this.tableTypes = tableTypes;
-    }
-
-    public String getNamingStrategy() {
-        return namingStrategy;
-    }
-
-    public void setNamingStrategy(String namingStrategy) {
-        this.namingStrategy = namingStrategy;
-    }
-
-    public FiltersConfig getFiltersConfig() {
-        if (filtersConfig == null) {
-            // this case is used often in tests where config not initialized properly
-            return FiltersConfig.create(null, null, TableFilter.everything(), PatternFilter.INCLUDE_NOTHING);
-        }
-        return filtersConfig;
-    }
-
-    public void setFiltersConfig(FiltersConfig filtersConfig) {
-        this.filtersConfig = filtersConfig;
-    }
-
-    public boolean isSkipRelationshipsLoading() {
-        return skipRelationshipsLoading != null && skipRelationshipsLoading;
-    }
-
-    public Boolean getSkipRelationshipsLoading() {
-        return skipRelationshipsLoading;
-    }
-
-    public void setSkipRelationshipsLoading(Boolean skipRelationshipsLoading) {
-        this.skipRelationshipsLoading = skipRelationshipsLoading;
-    }
-
-    public void setSkipPrimaryKeyLoading(Boolean skipPrimaryKeyLoading) {
-        this.skipPrimaryKeyLoading = skipPrimaryKeyLoading;
-    }
-
-    public boolean getSkipPrimaryKeyLoading() {
-        return skipPrimaryKeyLoading;
-    }
-
-    public boolean isSkipPrimaryKeyLoading() {
-        return skipPrimaryKeyLoading != null && skipPrimaryKeyLoading;
-    }
-
-    @Override
-    public String toString() {
-        String res = "EntitiesFilters: " + getFiltersConfig();
-        if (isSkipRelationshipsLoading()) {
-            res += "\n Skip Loading Relationships! \n";
-        }
-
-        if (isSkipPrimaryKeyLoading()) {
-            res += "\n Skip Loading PrimaryKeys! \n";
-        }
-
-        return res;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbTableLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbTableLoader.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbTableLoader.java
deleted file mode 100644
index baad305..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DbTableLoader.java
+++ /dev/null
@@ -1,196 +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.loader;
-
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.cayenne.access.DbLoaderDelegate;
-import org.apache.cayenne.access.loader.filters.PatternFilter;
-import org.apache.cayenne.access.loader.filters.TableFilter;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DetectedDbEntity;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * @since 4.0
- */
-public class DbTableLoader {
-
-	private static final Log LOGGER = LogFactory.getLog(DbTableLoader.class);
-
-	private static final String WILDCARD = "%";
-
-	private final String catalog;
-	private final String schema;
-
-	private final DatabaseMetaData metaData;
-	private final DbLoaderDelegate delegate;
-
-	private final DbAttributesLoader attributesLoader;
-
-	public DbTableLoader(String catalog, String schema, DatabaseMetaData metaData, DbLoaderDelegate delegate,
-			DbAttributesLoader attributesLoader) {
-		this.catalog = catalog;
-		this.schema = schema;
-		this.metaData = metaData;
-		this.delegate = delegate;
-
-		this.attributesLoader = attributesLoader;
-	}
-
-	/**
-	 * Returns all tables for given combination of the criteria. Tables returned
-	 * as DbEntities without any attributes or relationships.
-	 *
-	 * @param types
-	 *            The types of table names to retrieve, null returns all types.
-	 * @return
-	 * @since 4.0
-	 */
-	public List<DetectedDbEntity> getDbEntities(TableFilter filters, String[] types) throws SQLException {
-		if (LOGGER.isDebugEnabled()) {
-			LOGGER.debug("Read tables: catalog=" + catalog + ", schema=" + schema + ", types=" + Arrays.toString(types));
-		}
-
-		List<DetectedDbEntity> tables = new LinkedList<DetectedDbEntity>();
-		try (ResultSet rs = metaData.getTables(catalog, schema, WILDCARD, types);) {
-			while (rs.next()) {
-				// Oracle 9i and newer has a nifty recycle bin feature... but we
-				// don't
-				// want dropped tables to be included here; in fact they may
-				// even result
-				// in errors on reverse engineering as their names have special
-				// chars like
-				// "/", etc. So skip them all together
-
-				String name = rs.getString("TABLE_NAME");
-				if (name == null) {
-					continue;
-				}
-
-				DetectedDbEntity table = new DetectedDbEntity(name);
-
-				String catalog = rs.getString("TABLE_CAT");
-				table.setCatalog(catalog);
-
-				String schema = rs.getString("TABLE_SCHEM");
-				table.setSchema(schema);
-				if (!(this.catalog == null || this.catalog.equals(catalog))
-						|| !(this.schema == null || this.schema.equals(schema))) {
-
-					LOGGER.error(catalog + "." + schema + "." + name + " wrongly loaded for catalog/schema : "
-							+ this.catalog + "." + this.schema);
-
-					continue;
-				}
-
-				PatternFilter includeTable = filters.isIncludeTable(table.getName());
-				if (includeTable != null) {
-					tables.add(table);
-				}
-			}
-		}
-		return tables;
-	}
-
-	/**
-	 * Loads dbEntities for the specified tables.
-	 * 
-	 * @param config
-	 * @param types
-	 */
-	public List<DbEntity> loadDbEntities(DataMap map, DbLoaderConfiguration config, String[] types) throws SQLException {
-		/** List of db entities to process. */
-
-		List<DetectedDbEntity> tables = getDbEntities(config.getFiltersConfig().tableFilter(catalog, schema), types);
-
-		List<DbEntity> dbEntities = new ArrayList<DbEntity>();
-		for (DbEntity dbEntity : tables) {
-			DbEntity oldEnt = map.getDbEntity(dbEntity.getName());
-			if (oldEnt != null) {
-				Collection<ObjEntity> oldObjEnt = map.getMappedEntities(oldEnt);
-				if (!oldObjEnt.isEmpty()) {
-					for (ObjEntity objEntity : oldObjEnt) {
-						LOGGER.debug("Delete ObjEntity: " + objEntity.getName());
-						map.removeObjEntity(objEntity.getName(), true);
-						delegate.objEntityRemoved(objEntity);
-					}
-				}
-
-				LOGGER.debug("Overwrite DbEntity: " + oldEnt.getName());
-				map.removeDbEntity(oldEnt.getName(), true);
-				delegate.dbEntityRemoved(oldEnt);
-			}
-
-			map.addDbEntity(dbEntity);
-
-			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) {
-				dbEntities.add(dbEntity);
-				attributesLoader.loadDbAttributes(dbEntity);
-				if (!config.isSkipPrimaryKeyLoading()) {
-					loadPrimaryKey(dbEntity);
-				}
-			}
-		}
-
-		return dbEntities;
-	}
-
-	private void loadPrimaryKey(DbEntity dbEntity) throws SQLException {
-
-		try (ResultSet rs = metaData.getPrimaryKeys(dbEntity.getCatalog(), dbEntity.getSchema(), dbEntity.getName());) {
-			while (rs.next()) {
-				String columnName = rs.getString("COLUMN_NAME");
-				DbAttribute attribute = dbEntity.getAttribute(columnName);
-
-				if (attribute != null) {
-					attribute.setPrimaryKey(true);
-				} else {
-					// why an attribute might be null is not quiet clear
-					// but there is a bug report 731406 indicating that it is
-					// possible
-					// so just print the warning, and ignore
-					LOGGER.warn("Can't locate attribute for primary key: " + columnName);
-				}
-
-				String pkName = rs.getString("PK_NAME");
-				if (pkName != null && dbEntity instanceof DetectedDbEntity) {
-					((DetectedDbEntity) dbEntity).setPrimaryKeyName(pkName);
-				}
-
-			}
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DefaultDbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DefaultDbLoaderDelegate.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DefaultDbLoaderDelegate.java
deleted file mode 100644
index 70a0230..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/DefaultDbLoaderDelegate.java
+++ /dev/null
@@ -1,60 +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.loader;
-
-import org.apache.cayenne.access.DbLoaderDelegate;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjEntity;
-
-/**
- * @since 4.0.
- */
-public class DefaultDbLoaderDelegate implements DbLoaderDelegate {
-
-    @Override
-    public void dbEntityAdded(DbEntity entity) {
-
-    }
-
-    @Override
-    public void dbEntityRemoved(DbEntity entity) {
-
-    }
-
-    @Override
-    public boolean dbRelationship(DbEntity entity) {
-        return true;
-    }
-
-    @Override
-    public boolean dbRelationshipLoaded(DbEntity entity, DbRelationship relationship) {
-        return true;
-    }
-
-    @Override
-    public void objEntityAdded(ObjEntity entity) {
-
-    }
-
-    @Override
-    public void objEntityRemoved(ObjEntity entity) {
-
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/LoggingDbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/LoggingDbLoaderDelegate.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/LoggingDbLoaderDelegate.java
deleted file mode 100644
index 8c77a87..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/LoggingDbLoaderDelegate.java
+++ /dev/null
@@ -1,76 +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.loader;
-
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.commons.logging.Log;
-
-/**
- * @since 4.0
- */
-public class LoggingDbLoaderDelegate extends DefaultDbLoaderDelegate {
-
-    private final Log logger;
-
-    public LoggingDbLoaderDelegate(Log logger) {
-        this.logger = logger;
-    }
-
-    @Override
-    public void dbEntityAdded(DbEntity entity) {
-        logger.info("  Table: " + entity.getFullyQualifiedName());
-    }
-
-    @Override
-    public void dbEntityRemoved(DbEntity entity) {
-        logger.info("  Table removed: " + entity.getFullyQualifiedName());
-    }
-
-    @Override
-    public boolean dbRelationship(DbEntity entity) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("    Relationships for " + entity.getFullyQualifiedName());
-        }
-
-        return true;
-    }
-
-    @Override
-    public boolean dbRelationshipLoaded(DbEntity entity, DbRelationship relationship) {
-        logger.info("    " + relationship);
-
-        return true;
-    }
-
-    @Override
-    public void objEntityAdded(ObjEntity entity) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("  Class: " + entity.getName());
-        }
-    }
-
-    @Override
-    public void objEntityRemoved(ObjEntity entity) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("  Class removed: " + entity.getName());
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index 6b93f19..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntity.java
+++ /dev/null
@@ -1,142 +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.loader;
-
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.map.naming.DefaultUniqueNameGenerator;
-import org.apache.cayenne.map.naming.ExportedKey;
-import org.apache.cayenne.map.naming.NameCheckers;
-import org.apache.cayenne.map.naming.ObjectNameGenerator;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class represent ObjEntity that may be optimized using flattened relationships
- * as many to many table
- */
-public class ManyToManyCandidateEntity {
-
-    private static final Log LOG = LogFactory.getLog(ManyToManyCandidateEntity.class);
-
-    private final ObjEntity joinEntity;
-
-    private final DbRelationship dbRel1;
-    private final DbRelationship dbRel2;
-
-    private final ObjEntity entity1;
-    private final ObjEntity entity2;
-
-    private final DbRelationship reverseRelationship1;
-    private final DbRelationship reverseRelationship2;
-
-    private ManyToManyCandidateEntity(ObjEntity entityValue, List<ObjRelationship> relationships) {
-        joinEntity = entityValue;
-
-        ObjRelationship rel1 = relationships.get(0);
-        ObjRelationship rel2 = relationships.get(1);
-
-        dbRel1 = rel1.getDbRelationships().get(0);
-        dbRel2 = rel2.getDbRelationships().get(0);
-
-        reverseRelationship1 = dbRel1.getReverseRelationship();
-        reverseRelationship2 = dbRel2.getReverseRelationship();
-
-        entity1 = rel1.getTargetEntity();
-        entity2 = rel2.getTargetEntity();
-    }
-
-    /**
-     * Method check - if current entity represent many to many temporary table
-     * @return true if current entity is represent many to many table; otherwise returns false
-     */
-    public static ManyToManyCandidateEntity build(ObjEntity joinEntity) {
-        ArrayList<ObjRelationship> relationships = new ArrayList<ObjRelationship>(joinEntity.getRelationships());
-        if (relationships.size() != 2 || (relationships.get(0).getDbRelationships().isEmpty() || relationships.get(1).getDbRelationships().isEmpty())) {
-            return null;
-        }
-
-        ManyToManyCandidateEntity candidateEntity = new ManyToManyCandidateEntity(joinEntity, relationships);
-        if (candidateEntity.isManyToMany()) {
-            return candidateEntity;
-        }
-
-        return null;
-    }
-
-    private boolean isManyToMany() {
-        boolean isNotHaveAttributes = joinEntity.getAttributes().size() == 0;
-
-        return isNotHaveAttributes
-                && reverseRelationship1 != null && reverseRelationship1.isToDependentPK()
-                && reverseRelationship2 != null && reverseRelationship2.isToDependentPK()
-                && entity1 != null && entity2 != null;
-    }
-
-    private void addFlattenedRelationship(ObjectNameGenerator nameGenerator, ObjEntity srcEntity, ObjEntity dstEntity,
-                                          DbRelationship rel1, DbRelationship rel2) {
-
-        if (rel1.getSourceAttributes().isEmpty() && rel2.getTargetAttributes().isEmpty()) {
-            LOG.warn("Wrong call ManyToManyCandidateEntity.addFlattenedRelationship(... , " + srcEntity.getName()
-                    + ", " + dstEntity.getName() + ", ...)");
-
-            return;
-        }
-
-        ExportedKey key = new ExportedKey(
-                rel1.getSourceEntity().getName(),
-                rel1.getSourceAttributes().iterator().next().getName(),
-                null,
-                rel2.getTargetEntity().getName(),
-                rel2.getTargetAttributes().iterator().next().getName(),
-                null,
-                (short) 1);
-
-        ObjRelationship newRelationship = new ObjRelationship();
-        newRelationship.setName(DefaultUniqueNameGenerator.generate(NameCheckers.objRelationship, srcEntity,
-                nameGenerator.createDbRelationshipName(key, true)));
-
-        newRelationship.setSourceEntity(srcEntity);
-        newRelationship.setTargetEntityName(dstEntity);
-
-        newRelationship.addDbRelationship(rel1);
-        newRelationship.addDbRelationship(rel2);
-
-        srcEntity.addRelationship(newRelationship);
-    }
-
-    /**
-     * Method make direct relationships between 2 entities and remove relationships to
-     * many to many entity
-     *
-     * @param nameGenerator
-     */
-    public void optimizeRelationships(ObjectNameGenerator nameGenerator) {
-        entity1.removeRelationship(reverseRelationship1.getName());
-        entity2.removeRelationship(reverseRelationship2.getName());
-
-        addFlattenedRelationship(nameGenerator, entity1, entity2, reverseRelationship1, dbRel2);
-        addFlattenedRelationship(nameGenerator, entity2, entity1, reverseRelationship2, dbRel1);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NameFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NameFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NameFilter.java
deleted file mode 100644
index b0269d4..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NameFilter.java
+++ /dev/null
@@ -1,27 +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.loader;
-
-/**
- * @since 4.0.
- */
-public interface NameFilter {
-
-    boolean isIncluded(String string);
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NamePatternMatcher.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NamePatternMatcher.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NamePatternMatcher.java
deleted file mode 100644
index 6faa7e4..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/NamePatternMatcher.java
+++ /dev/null
@@ -1,225 +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.loader;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-import org.apache.cayenne.util.CayenneMapEntry;
-import org.apache.commons.logging.Log;
-
-/**
- * Provides name pattern matching functionality.
- * 
- * @since 1.2
- */
-public class NamePatternMatcher implements NameFilter {
-
-    private static final String[] EMPTY_ARRAY = new String[0];
-    private static final Pattern COMMA = Pattern.compile(",");
-
-    private final Pattern[] itemIncludeFilters;
-    private final Pattern[] itemExcludeFilters;
-
-    public static NamePatternMatcher build(Log logger, String includePattern, String excludePattern) {
-        return new NamePatternMatcher(createPatterns(logger, includePattern), createPatterns(logger, excludePattern));
-    }
-
-    public NamePatternMatcher(Pattern[] itemIncludeFilters, Pattern[] itemExcludeFilters) {
-        this.itemIncludeFilters = itemIncludeFilters;
-        this.itemExcludeFilters = itemExcludeFilters;
-    }
-
-    /**
-     * Applies preconfigured list of filters to the list, removing entities that do not
-     * pass the filter.
-     * 
-     * @deprecated since 3.0 still used by AntDataPortDelegate, which itself should
-     *             probably be deprecated
-     */
-    @Deprecated
-    public List<?> filter(List<?> items) {
-        if (items == null || items.isEmpty()) {
-            return items;
-        }
-
-        if (itemIncludeFilters.length == 0 && itemExcludeFilters.length == 0) {
-            return items;
-        }
-
-        Iterator<?> it = items.iterator();
-        while (it.hasNext()) {
-            CayenneMapEntry entity = (CayenneMapEntry) it.next();
-
-            if (!passedIncludeFilter(entity.getName())) {
-                it.remove();
-                continue;
-            }
-
-            if (!passedExcludeFilter(entity.getName())) {
-                it.remove();
-            }
-        }
-
-        return items;
-    }
-
-    /**
-     * Returns an array of Patterns. Takes a comma-separated list of patterns, attempting
-     * to convert them to the java.util.regex.Pattern syntax. E.g.
-     * <p>
-     * <code>"billing_*,user?"</code> will become an array of two expressions:
-     * <p>
-     * <code>^billing_.*$</code><br>
-     * <code>^user.?$</code><br>
-     */
-    public static Pattern[] createPatterns(Log logger, String patternString) {
-        if (patternString == null) {
-            return new Pattern[0];
-        }
-        String[] patternStrings = tokenizePattern(patternString);
-        List<Pattern> patterns = new ArrayList<Pattern>(patternStrings.length);
-
-        for (String patternString1 : patternStrings) {
-
-            // test the pattern
-            try {
-                patterns.add(Pattern.compile(patternString1));
-            } catch (PatternSyntaxException e) {
-
-                if (logger != null) {
-                    logger.warn("Ignoring invalid pattern [" + patternString1 + "], reason: " + e.getMessage());
-                }
-            }
-        }
-
-        return patterns.toArray(new Pattern[patterns.size()]);
-    }
-
-    /**
-     * Returns an array of valid regular expressions. Takes a comma-separated list of
-     * patterns, attempting to convert them to the java.util.regex.Pattern syntax. E.g.
-     * <p>
-     * <code>"billing_*,user?"</code> will become an array of two expressions:
-     * <p>
-     * <code>^billing_.*$</code><br>
-     * <code>^user.?$</code><br>
-     */
-    public static String[] tokenizePattern(String pattern) {
-        if (pattern == null || pattern.isEmpty()) {
-            return EMPTY_ARRAY;
-        }
-
-        String[] patterns = COMMA.split(pattern);
-        if (patterns.length == 0) {
-            return EMPTY_ARRAY;
-        }
-
-        for (int i = 0; i < patterns.length; i++) {
-            // convert * into regex syntax
-            // e.g. abc*x becomes ^abc.*x$
-            // or abc?x becomes ^abc.?x$
-            patterns[i] = "^" + patterns[i].replaceAll("[*?]", ".$0") + "$";
-        }
-
-        return patterns;
-    }
-
-    /**
-     * Returns true if a given object property satisfies the include/exclude patterns.
-     * 
-     * @since 3.0
-     */
-    @Override
-    public boolean isIncluded(String string) {
-        return passedIncludeFilter(string) && passedExcludeFilter(string);
-    }
-
-    /**
-     * Returns true if an object matches any one of the "include" patterns, or if there is
-     * no "include" patterns defined.
-     * 
-     * @since 3.0
-     */
-    private boolean passedIncludeFilter(String item) {
-        if (itemIncludeFilters.length == 0) {
-            return true;
-        }
-
-        for (Pattern itemIncludeFilter : itemIncludeFilters) {
-            if (itemIncludeFilter.matcher(item).find()) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Returns true if an object does not match any one of the "exclude" patterns, or if
-     * there is no "exclude" patterns defined.
-     * 
-     * @since 3.0
-     */
-    private boolean passedExcludeFilter(String item) {
-        if (itemExcludeFilters.length == 0) {
-            return true;
-        }
-
-        for (Pattern itemExcludeFilter : itemExcludeFilters) {
-            if (itemExcludeFilter.matcher(item).find()) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    public static String replaceWildcardInStringWithString(
-            String wildcard,
-            String pattern,
-            String replacement) {
-
-        if (pattern == null || wildcard == null) {
-            return pattern;
-        }
-
-        StringBuilder buffer = new StringBuilder();
-        int lastPos = 0;
-        int wildCardPos = pattern.indexOf(wildcard);
-        while (wildCardPos != -1) {
-            if (lastPos != wildCardPos) {
-                buffer.append(pattern.substring(lastPos, wildCardPos));
-            }
-            buffer.append(replacement);
-            lastPos += wildCardPos + wildcard.length();
-            wildCardPos = pattern.indexOf(wildcard, lastPos);
-        }
-
-        if (lastPos < pattern.length()) {
-            buffer.append(pattern.substring(lastPos));
-        }
-
-        return buffer.toString();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/CatalogFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/CatalogFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/CatalogFilter.java
deleted file mode 100644
index 830c9c8..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/CatalogFilter.java
+++ /dev/null
@@ -1,62 +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.loader.filters;
-
-import java.util.Arrays;
-
-/**
-* @since 4.0.
-*/
-public class CatalogFilter {
-    public final String name;
-    public final SchemaFilter[] schemas;
-
-    public CatalogFilter(String name, SchemaFilter... schemas) {
-        if (schemas == null || schemas.length == 0) {
-            throw new IllegalArgumentException("schemas(" + Arrays.toString(schemas) + ") can't be null or empty");
-        }
-
-        this.name = name;
-        this.schemas = schemas;
-    }
-
-    public SchemaFilter getSchema(String schema) {
-        for (SchemaFilter schemaFilter : schemas) {
-            if (schemaFilter.name == null || schemaFilter.name.equals(schema)) {
-                return schemaFilter;
-            }
-        }
-
-        return null;
-    }
-
-    @Override
-    public String toString() {
-        return toString(new StringBuilder(), "").toString();
-    }
-
-    public StringBuilder toString(StringBuilder res, String prefix) {
-        res.append(prefix).append("Catalog: ").append(name).append("\n");
-        for (SchemaFilter schema : schemas) {
-            schema.toString(res, prefix + "  ");
-        }
-
-        return res;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FiltersConfig.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FiltersConfig.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FiltersConfig.java
deleted file mode 100644
index 7a6823e..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/FiltersConfig.java
+++ /dev/null
@@ -1,81 +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.loader.filters;
-
-import java.util.Arrays;
-
-/**
- * @since 4.0.
- */
-public class FiltersConfig {
-
-    public final CatalogFilter[] catalogs;
-
-    public FiltersConfig(CatalogFilter ... catalogs) {
-        if (catalogs == null || catalogs.length == 0) {
-            throw new IllegalArgumentException("catalogs(" + Arrays.toString(catalogs) + ") can't be null or empty");
-        }
-
-        this.catalogs = catalogs;
-    }
-
-    public PatternFilter proceduresFilter(String catalog, String schema) {
-        return getSchemaFilter(catalog, schema).procedures;
-    }
-
-    public TableFilter tableFilter(String catalog, String schema) {
-        return getSchemaFilter(catalog, schema).tables;
-    }
-
-    protected SchemaFilter getSchemaFilter(String catalog, String schema) {
-        CatalogFilter catalogFilter = getCatalog(catalog);
-        if (catalogFilter == null) {
-            return null;
-        }
-
-        return catalogFilter.getSchema(schema);
-    }
-
-    protected CatalogFilter getCatalog(String catalog) {
-        for (CatalogFilter catalogFilter : catalogs) {
-            if (catalogFilter.name == null || catalogFilter.name.equals(catalog)) {
-                return catalogFilter;
-            }
-        }
-
-        return null;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder builder = new StringBuilder();
-        for (CatalogFilter catalog : catalogs) {
-            catalog.toString(builder, "");
-        }
-
-        return builder.toString();
-    }
-
-    public static FiltersConfig create(String catalog, String schema, TableFilter tableFilter, PatternFilter procedures) {
-        return new FiltersConfig(
-                    new CatalogFilter(catalog,
-                        new SchemaFilter(schema, tableFilter, procedures)));
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/IncludeTableFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/IncludeTableFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/IncludeTableFilter.java
deleted file mode 100644
index dcb144b..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/IncludeTableFilter.java
+++ /dev/null
@@ -1,71 +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.loader.filters;
-
-import java.util.regex.Pattern;
-
-/**
-* @since 4.0.
-*/
-public class IncludeTableFilter implements Comparable<IncludeTableFilter> {
-    public final Pattern pattern;
-
-    public final PatternFilter columnsFilter;
-
-    public IncludeTableFilter(String pattern) {
-        this(pattern, PatternFilter.INCLUDE_EVERYTHING);
-    }
-
-    public IncludeTableFilter(String pattern, PatternFilter columnsFilter) {
-        this.pattern = PatternFilter.pattern(pattern);
-        this.columnsFilter = columnsFilter;
-    }
-
-    public boolean isIncludeColumn (String name) {
-        return columnsFilter.isInclude(name);
-    }
-
-    @Override
-    public int compareTo(IncludeTableFilter o) {
-        if (pattern == null && o.pattern == null) {
-            return 0;
-        } else if (pattern == null) {
-            return 1;
-        } else if (o.pattern == null) {
-            return -1;
-        } else {
-            return pattern.pattern().compareTo(o.pattern.pattern());
-        }
-
-    }
-
-
-    @Override
-    public String toString() {
-        return toString(new StringBuilder(), "").toString();
-    }
-
-    protected StringBuilder toString(StringBuilder res, String prefix) {
-        res.append(prefix).append("Include: ").append(String.valueOf(pattern)).append(" Columns: ");
-        columnsFilter.toString(res);
-        res.append("\n");
-
-        return res;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/LegacyFilterConfigBridge.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/LegacyFilterConfigBridge.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/LegacyFilterConfigBridge.java
deleted file mode 100644
index 90545c6..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/LegacyFilterConfigBridge.java
+++ /dev/null
@@ -1,150 +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.loader.filters;
-
-import static org.apache.commons.lang.StringUtils.isBlank;
-
-/**
- * @since 4.0
- */
-public class LegacyFilterConfigBridge {
-
-    private String catalog;
-    private String schema;
-
-    private String includeTableFilters;
-    private String includeColumnFilters;
-    private String includeProceduresFilters;
-    private String excludeTableFilters;
-    private String excludeColumnFilters;
-    private String excludeProceduresFilters;
-
-    private boolean loadProcedures;
-
-    public LegacyFilterConfigBridge() {
-    }
-
-    public LegacyFilterConfigBridge catalog(String catalog) {
-        this.catalog = catalog;
-        return this;
-    }
-
-    public String catalog() {
-        return catalog;
-    }
-
-    public LegacyFilterConfigBridge schema(String schema) {
-        this.schema = schema;
-        return this;
-    }
-
-    public String schema() {
-        return schema;
-    }
-
-    public LegacyFilterConfigBridge includeTables(String tableFilters) {
-        if (isBlank(tableFilters)) {
-            return this;
-        }
-
-        this.includeTableFilters = transform(tableFilters);
-        return this;
-    }
-
-    public LegacyFilterConfigBridge includeColumns(String columnFilters) {
-        if (isBlank(columnFilters)) {
-            return this;
-        }
-
-        this.includeColumnFilters = transform(columnFilters);
-        return this;
-    }
-
-    public LegacyFilterConfigBridge includeProcedures(String proceduresFilters) {
-        if (isBlank(proceduresFilters)) {
-            return this;
-        }
-
-        this.includeProceduresFilters = transform(proceduresFilters);
-        return this;
-    }
-
-    public LegacyFilterConfigBridge excludeTables(String tableFilters) {
-        if (isBlank(tableFilters)) {
-            return this;
-        }
-
-        this.excludeTableFilters = transform(tableFilters);
-        return this;
-    }
-
-    public LegacyFilterConfigBridge excludeColumns(String columnFilters) {
-        if (isBlank(columnFilters)) {
-            return this;
-        }
-
-        this.excludeColumnFilters = transform(columnFilters);
-        return this;
-    }
-
-    public LegacyFilterConfigBridge excludeProcedures(String proceduresFilters) {
-        if (isBlank(proceduresFilters)) {
-            return this;
-        }
-
-        this.excludeProceduresFilters = transform(proceduresFilters);
-        return this;
-    }
-
-    private static String transform(String pattern) {
-        return "^" + pattern.replaceAll("[*?]", ".$0") + "$";
-    }
-
-    public void setProceduresFilters(boolean loadProcedures) {
-        this.loadProcedures = loadProcedures;
-    }
-
-    public String getIncludeTableFilters() {
-        return includeTableFilters;
-    }
-
-    public String getIncludeColumnFilters() {
-        return includeColumnFilters;
-    }
-
-    public String getIncludeProceduresFilters() {
-        return includeProceduresFilters;
-    }
-
-    public String getExcludeTableFilters() {
-        return excludeTableFilters;
-    }
-
-    public String getExcludeColumnFilters() {
-        return excludeColumnFilters;
-    }
-
-    public String getExcludeProceduresFilters() {
-        return excludeProceduresFilters;
-    }
-
-    public boolean isLoadProcedures() {
-        return loadProcedures;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/PatternFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/PatternFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/PatternFilter.java
deleted file mode 100644
index 82c45cc..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/PatternFilter.java
+++ /dev/null
@@ -1,169 +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.loader.filters;
-
-import org.apache.commons.lang.StringUtils;
-
-import java.util.Comparator;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-/**
- * @since 4.0
- */
-public class PatternFilter {
-
-    public static final PatternFilter INCLUDE_EVERYTHING = new PatternFilter() {
-
-        @Override
-        public boolean isInclude(String obj) {
-            return true;
-        }
-
-        @Override
-        public StringBuilder toString(StringBuilder res) {
-            return res.append("ALL");
-        }
-    };
-
-    public static final PatternFilter INCLUDE_NOTHING = new PatternFilter() {
-        @Override
-        public boolean isInclude(String obj) {
-            return false;
-        }
-
-        @Override
-        public StringBuilder toString(StringBuilder res) {
-            return res.append("NONE");
-        }
-    };
-
-    public static final Comparator<Pattern> PATTERN_COMPARATOR = new Comparator<Pattern>() {
-        @Override
-        public int compare(Pattern o1, Pattern o2) {
-            if (o1 != null && o2 != null) {
-                return o1.pattern().compareTo(o2.pattern());
-            }
-            else {
-                return -1;
-            }
-        }
-    };
-
-    private final SortedSet<Pattern> includes;
-    private final SortedSet<Pattern> excludes;
-
-    public PatternFilter() {
-        this.includes = new TreeSet<>(PATTERN_COMPARATOR);
-        this.excludes = new TreeSet<>(PATTERN_COMPARATOR);
-    }
-
-    public PatternFilter include(Pattern p) {
-        includes.add(p);
-
-        return this;
-    }
-
-    public PatternFilter exclude(Pattern p) {
-        excludes.add(p);
-
-        return this;
-    }
-
-    public static Pattern pattern(String pattern) {
-        if (pattern == null) {
-            return null;
-        }
-        return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
-    }
-
-    public PatternFilter include(String p) {
-        return include(pattern(p));
-    }
-
-    public PatternFilter exclude(String p) {
-        return exclude(pattern(p));
-    }
-
-    public boolean isInclude(String obj) {
-        boolean include = includes.isEmpty();
-        for (Pattern p : includes) {
-            if (p != null) {
-                if (p.matcher(obj).matches()) {
-                    include = true;
-                    break;
-                }
-            }
-        }
-
-        if (!include) {
-            return false;
-        }
-
-        for (Pattern p : excludes) {
-            if (p.matcher(obj).matches()) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-
-        PatternFilter filter = (PatternFilter) o;
-        return includes.equals(filter.includes)
-                && excludes.equals(filter.excludes);
-    }
-
-    @Override
-    public int hashCode() {
-        return includes.hashCode();
-    }
-
-    public StringBuilder toString(StringBuilder res) {
-        if (includes.isEmpty()) {
-            // Do nothing.
-        } else if (includes.size() > 1) {
-            res.append("(").append(StringUtils.join(includes, " OR ")).append(")");
-        } else {
-            res.append(includes.first().pattern());
-        }
-
-        if (!excludes.isEmpty()) {
-            res.append(" AND NOT (").append(StringUtils.join(includes, " OR ")).append(")");
-        }
-
-        return res;
-    }
-
-    public boolean isEmpty() {
-        return includes.isEmpty() && excludes.isEmpty();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/SchemaFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/SchemaFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/SchemaFilter.java
deleted file mode 100644
index c294bc4..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/SchemaFilter.java
+++ /dev/null
@@ -1,44 +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.loader.filters;
-
-/**
-* @since 4.0.
-*/
-public class SchemaFilter {
-    public final String name;
-    public final TableFilter tables;
-    public final PatternFilter procedures;
-
-    public SchemaFilter(String name, TableFilter tables, PatternFilter procedures) {
-        this.name = name;
-        this.tables = tables;
-        this.procedures = procedures;
-    }
-
-    protected StringBuilder toString(StringBuilder res, String prefix) {
-        res.append(prefix).append("Schema: ").append(name).append("\n");
-        tables.toString(res, prefix + "  ");
-
-        res.append(prefix).append("  Procedures: ");
-        procedures.toString(res).append("\n");
-
-        return res;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/TableFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/TableFilter.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/TableFilter.java
deleted file mode 100644
index 7d3c8f6..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/filters/TableFilter.java
+++ /dev/null
@@ -1,133 +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.loader.filters;
-
-import org.apache.commons.lang.StringUtils;
-
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-/**
- * TableFilter contain at least one IncludeTable always
- *
- */
-public class TableFilter {
-
-    private final SortedSet<IncludeTableFilter> includes;
-    private final SortedSet<Pattern> excludes;
-
-    /**
-     * Includes can contain only One includetable
-     *
-     * @param includes
-     * @param excludes
-     */
-    public TableFilter(SortedSet<IncludeTableFilter> includes, SortedSet<Pattern> excludes) {
-        if (includes.isEmpty()) {
-            throw new IllegalArgumentException("TableFilter should contain at least one IncludeTableFilter always " +
-                    "and it is builder responsibility. If you need table filter without includes, use EmptyTableFilter");
-        }
-
-        this.includes = includes;
-        this.excludes = excludes;
-    }
-
-    /**
-     * Return filter for columns in case we should take this table
-     *
-     * @param tableName
-     * @return
-     */
-    public PatternFilter isIncludeTable(String tableName) {
-        IncludeTableFilter include = null;
-        for (IncludeTableFilter p : includes) {
-            if (p.pattern == null || p.pattern.matcher(tableName).matches()) {
-                include = p;
-                break;
-            }
-        }
-
-        if (include == null) {
-            return null;
-        }
-
-        for (Pattern p : excludes) {
-            if (p != null) {
-                if (p.matcher(tableName).matches()) {
-                    return null;
-                }
-            }
-        }
-
-        return include.columnsFilter;
-    }
-
-    public static TableFilter include(String tablePattern) {
-        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
-        includes.add(new IncludeTableFilter(tablePattern == null ? null : tablePattern.replaceAll("%", ".*")));
-
-        return new TableFilter(includes, new TreeSet<Pattern>());
-    }
-
-    public static TableFilter everything() {
-        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
-        includes.add(new IncludeTableFilter(null));
-
-        return new TableFilter(includes, new TreeSet<Pattern>());
-    }
-
-    protected StringBuilder toString(StringBuilder res, String prefix) {
-        res.append(prefix).append("Tables: ").append("\n");
-
-        for (IncludeTableFilter include : includes) {
-            include.toString(res, prefix + "  ");
-        }
-
-        if (!excludes.isEmpty()) {
-            res.append(prefix).append("  ").append(StringUtils.join(excludes, " OR ")).append("\n");
-        }
-
-        return res;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-
-        if (!(o instanceof TableFilter)) {
-            return false;
-        }
-
-        TableFilter that = (TableFilter) o;
-
-        return excludes.equals(that.excludes)
-                && includes.equals(that.includes);
-
-    }
-
-    @Override
-    public int hashCode() {
-        int result = includes.hashCode();
-        result = 31 * result + excludes.hashCode();
-        return result;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DbType.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DbType.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DbType.java
deleted file mode 100644
index 806b437..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DbType.java
+++ /dev/null
@@ -1,194 +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.loader.mapper;
-
-import org.apache.cayenne.util.EqualsBuilder;
-import org.apache.cayenne.util.HashCodeBuilder;
-import org.apache.commons.lang.builder.CompareToBuilder;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import static org.apache.commons.lang.StringUtils.isBlank;
-
-/**
- * @since 4.0
- */
-public class DbType implements Comparable<DbType> {
-
-    private static final Log LOG = LogFactory.getLog(DbType.class);
-
-    public final String jdbc;
-
-    public final Integer length;
-    public final Integer precision;
-    public final Integer scale;
-    public final Boolean notNull;
-
-    public DbType(String jdbc) {
-        this(jdbc, null, null, null, null);
-    }
-
-    public DbType(String jdbc, Integer length, Integer precision, Integer scale, Boolean notNull) {
-        if (isBlank(jdbc)) {
-            throw new IllegalArgumentException("Jdbc type can't be null");
-        }
-        this.jdbc = jdbc;
-
-        this.length = getValidInt(length);
-        this.precision = getValidInt(precision);
-        this.scale = getValidInt(scale);
-        this.notNull = notNull;
-    }
-
-    public String getJdbc() {
-        return jdbc;
-    }
-
-    public Integer getLength() {
-        return length;
-    }
-
-    public Integer getPrecision() {
-        return precision;
-    }
-
-    public Integer getScale() {
-        return scale;
-    }
-
-    public Boolean getNotNull() {
-        return notNull;
-    }
-
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == null) {
-            return false;
-        }
-        if (obj == this) {
-            return true;
-        }
-        if (obj.getClass() != getClass()) {
-            return false;
-        }
-        DbType rhs = (DbType) obj;
-        return new EqualsBuilder()
-                .append(this.jdbc, rhs.jdbc)
-                .append(this.length, rhs.length)
-                .append(this.precision, rhs.precision)
-                .append(this.scale, rhs.scale)
-                .append(this.notNull, rhs.notNull)
-                .isEquals();
-    }
-
-    @Override
-    public int hashCode() {
-        return new HashCodeBuilder()
-                .append(jdbc)
-                .append(length)
-                .append(precision)
-                .append(scale)
-                .append(notNull)
-                .toHashCode();
-    }
-
-
-    @Override
-    public String toString() {
-        String res = jdbc;
-
-        String len = "*";
-        if (isPositive(length)) {
-            len = length.toString();
-        }
-        if (isPositive(precision)) {
-            len = precision.toString();
-        }
-
-        res += " (" + len;
-        if (isPositive(scale)) {
-            res += ", " + scale;
-        }
-        res += ")";
-
-        if (notNull != null && notNull) {
-            res += " NOT NULL";
-        }
-
-        return res;
-    }
-
-    private boolean isPositive(Integer num) {
-        return num != null && num > 0;
-    }
-
-    private Integer getValidInt(Integer num) {
-        if (num == null || num > 0) {
-            return num;
-        }
-
-        LOG.warn("Invalid int value '" + num + "'");
-        return null;
-    }
-
-    /**
-     * Compare by specificity the most specific DbPath should be first in ordered list
-     */
-    @Override
-    public int compareTo(DbType dbType) {
-        return new CompareToBuilder()
-                .append(dbType.jdbc, jdbc)
-                .append(dbType.getSpecificity(), getSpecificity())
-                .append(dbType.length, length)
-                .append(dbType.precision, precision)
-                .append(dbType.scale, scale)
-                .append(dbType.notNull, notNull)
-                .toComparison();
-    }
-
-    private int getSpecificity() {
-        int res = 0;
-        if (isPositive(length)) {
-            res += 100;
-        }
-        if (isPositive(precision)) {
-            res += 100;
-        }
-        if (isPositive(scale)) {
-            res += 10;
-        }
-        if (this.notNull != null) {
-            res += 5;
-        }
-
-        return res;
-    }
-
-    public boolean isCover(DbType type) {
-        return this.jdbc.equals(type.jdbc)
-            && (isCover(length, type.length) || length == null && type.length == null && isCover(precision, type.precision))
-            && isCover(scale, type.scale)
-            && isCover(notNull, type.notNull);
-    }
-
-    private boolean isCover(Object a, Object b) {
-        return a == null || a.equals(b);
-    }
-}


[15/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
CAY-2116 Split schema synchronization code in a separate module


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

Branch: refs/heads/master
Commit: 2f7b1d53388883a2e341e9156bddd8d694f7ef88
Parents: 8c7bec0
Author: Andrus Adamchik <an...@objectstyle.com>
Authored: Thu Sep 29 20:33:54 2016 +0300
Committer: Andrus Adamchik <an...@objectstyle.com>
Committed: Thu Sep 29 20:38:29 2016 +0300

----------------------------------------------------------------------
 assembly/pom.xml                                |   6 +
 .../resources/assemblies/assembly-generic.xml   |   1 +
 .../main/resources/assemblies/assembly-mac.xml  |   1 +
 .../resources/assemblies/assembly-windows.xml   |   1 +
 cayenne-dbsync/pom.xml                          | 117 +++
 .../cayenne/dbsync/CayenneDbSyncModule.java     |  82 ++
 .../cayenne/dbsync/merge/AbstractToDbToken.java | 126 +++
 .../dbsync/merge/AbstractToModelToken.java      | 122 +++
 .../cayenne/dbsync/merge/AddColumnToDb.java     |  65 ++
 .../cayenne/dbsync/merge/AddColumnToModel.java  |  55 ++
 .../dbsync/merge/AddRelationshipToDb.java       |  87 ++
 .../dbsync/merge/AddRelationshipToModel.java    |  95 +++
 .../cayenne/dbsync/merge/CreateTableToDb.java   |  66 ++
 .../dbsync/merge/CreateTableToModel.java        | 102 +++
 .../apache/cayenne/dbsync/merge/DbMerger.java   | 405 +++++++++
 .../cayenne/dbsync/merge/DbMergerConfig.java    |  63 ++
 .../dbsync/merge/DefaultModelMergeDelegate.java |  89 ++
 .../merge/DefaultValueForNullProvider.java      |  62 ++
 .../cayenne/dbsync/merge/DropColumnToDb.java    |  52 ++
 .../cayenne/dbsync/merge/DropColumnToModel.java |  75 ++
 .../dbsync/merge/DropRelationshipToDb.java      |  72 ++
 .../dbsync/merge/DropRelationshipToModel.java   |  51 ++
 .../cayenne/dbsync/merge/DropTableToDb.java     |  50 ++
 .../cayenne/dbsync/merge/DropTableToModel.java  |  49 ++
 .../cayenne/dbsync/merge/DummyReverseToken.java |  60 ++
 .../dbsync/merge/EmptyValueForNullProvider.java |  40 +
 .../dbsync/merge/EntityMergeSupport.java        | 522 ++++++++++++
 .../cayenne/dbsync/merge/MergeDirection.java    |  70 ++
 .../cayenne/dbsync/merge/MergerContext.java     | 118 +++
 .../cayenne/dbsync/merge/MergerToken.java       |  53 ++
 .../dbsync/merge/ModelMergeDelegate.java        |  62 ++
 .../dbsync/merge/ProxyModelMergeDelegate.java   | 108 +++
 .../cayenne/dbsync/merge/SetAllowNullToDb.java  |  57 ++
 .../dbsync/merge/SetAllowNullToModel.java       |  43 +
 .../cayenne/dbsync/merge/SetColumnTypeToDb.java | 119 +++
 .../dbsync/merge/SetColumnTypeToModel.java      | 102 +++
 .../cayenne/dbsync/merge/SetNotNullToDb.java    |  51 ++
 .../cayenne/dbsync/merge/SetNotNullToModel.java |  43 +
 .../cayenne/dbsync/merge/SetPrimaryKeyToDb.java |  86 ++
 .../dbsync/merge/SetPrimaryKeyToModel.java      |  78 ++
 .../dbsync/merge/SetValueForNullToDb.java       |  47 ++
 .../dbsync/merge/ValueForNullProvider.java      |  42 +
 .../merge/factory/DB2MergerTokenFactory.java    |  47 ++
 .../factory/DefaultMergerTokenFactory.java      | 181 ++++
 .../merge/factory/DerbyMergerTokenFactory.java  |  85 ++
 .../factory/FirebirdMergerTokenFactory.java     |  91 ++
 .../merge/factory/H2MergerTokenFactory.java     |  82 ++
 .../merge/factory/HSQLMergerTokenFactory.java   |  82 ++
 .../merge/factory/IngresMergerTokenFactory.java | 224 +++++
 .../merge/factory/MergerTokenFactory.java       |  88 ++
 .../factory/MergerTokenFactoryProvider.java     |  36 +
 .../merge/factory/MySQLMergerTokenFactory.java  | 156 ++++
 .../factory/OpenBaseMergerTokenFactory.java     | 143 ++++
 .../merge/factory/OracleMergerTokenFactory.java | 111 +++
 .../factory/PostgresMergerTokenFactory.java     |  49 ++
 .../factory/SQLServerMergerTokenFactory.java    | 112 +++
 .../merge/factory/SybaseMergerTokenFactory.java | 166 ++++
 .../dbsync/reverse/DbAttributesBaseLoader.java  | 107 +++
 .../dbsync/reverse/DbAttributesLoader.java      |  43 +
 .../reverse/DbAttributesPerSchemaLoader.java    | 130 +++
 .../apache/cayenne/dbsync/reverse/DbLoader.java | 829 ++++++++++++++++++
 .../dbsync/reverse/DbLoaderConfiguration.java   | 150 ++++
 .../dbsync/reverse/DbLoaderDelegate.java        |  58 ++
 .../cayenne/dbsync/reverse/DbTableLoader.java   | 195 +++++
 .../dbsync/reverse/DefaultDbLoaderDelegate.java |  59 ++
 .../dbsync/reverse/FiltersConfigBuilder.java    | 393 +++++++++
 .../dbsync/reverse/LoggingDbLoaderDelegate.java |  76 ++
 .../reverse/ManyToManyCandidateEntity.java      | 142 ++++
 .../cayenne/dbsync/reverse/NameFilter.java      |  27 +
 .../dbsync/reverse/NamePatternMatcher.java      | 225 +++++
 .../dbsync/reverse/filters/CatalogFilter.java   |  62 ++
 .../dbsync/reverse/filters/FiltersConfig.java   |  81 ++
 .../reverse/filters/IncludeTableFilter.java     |  71 ++
 .../filters/LegacyFilterConfigBridge.java       | 150 ++++
 .../dbsync/reverse/filters/PatternFilter.java   | 169 ++++
 .../dbsync/reverse/filters/SchemaFilter.java    |  44 +
 .../dbsync/reverse/filters/TableFilter.java     | 133 +++
 .../cayenne/dbsync/reverse/mapper/DbType.java   | 194 +++++
 .../mapper/DefaultJdbc2JavaTypeMapper.java      | 282 +++++++
 .../reverse/mapper/Jdbc2JavaTypeMapper.java     |  33 +
 .../dbsync/merge/AddColumnToModelIT.java        |  98 +++
 .../dbsync/merge/CreateTableToModelIT.java      |  97 +++
 .../cayenne/dbsync/merge/DbMergerTest.java      | 261 ++++++
 .../dbsync/merge/DropColumnToModelIT.java       | 235 ++++++
 .../dbsync/merge/DropRelationshipToModelIT.java | 188 +++++
 .../dbsync/merge/DropTableToModelIT.java        |  94 +++
 .../dbsync/merge/EntityMergeSupportIT.java      | 102 +++
 .../apache/cayenne/dbsync/merge/MergeCase.java  | 209 +++++
 .../cayenne/dbsync/merge/MergerFactoryIT.java   | 310 +++++++
 .../dbsync/merge/SetAllowNullToDbIT.java        |  66 ++
 .../cayenne/dbsync/merge/SetNotNullToDbIT.java  |  62 ++
 .../dbsync/merge/SetPrimaryKeyToDbIT.java       |  58 ++
 .../cayenne/dbsync/merge/TokensReversTest.java  |  89 ++
 .../merge/TokensToModelExecutionTest.java       |  80 ++
 .../cayenne/dbsync/merge/ValueForNullIT.java    | 127 +++
 .../cayenne/dbsync/merge/builders/Builder.java  |  38 +
 .../dbsync/merge/builders/DataMapBuilder.java   | 128 +++
 .../merge/builders/DbAttributeBuilder.java      | 115 +++
 .../dbsync/merge/builders/DbEntityBuilder.java  |  90 ++
 .../merge/builders/DbRelationshipBuilder.java   |  85 ++
 .../dbsync/merge/builders/DefaultBuilder.java   |  57 ++
 .../merge/builders/ObjAttributeBuilder.java     |  67 ++
 .../dbsync/merge/builders/ObjEntityBuilder.java |  98 +++
 .../dbsync/merge/builders/ObjectMother.java     |  70 ++
 .../cayenne/dbsync/reverse/DbLoaderIT.java      | 430 ++++++++++
 .../dbsync/reverse/DbLoaderPartialIT.java       | 116 +++
 .../reverse/FiltersConfigBuilderTest.java       | 391 +++++++++
 .../reverse/ManyToManyCandidateEntityTest.java  | 113 +++
 .../reverse/filters/FiltersConfigTest.java      |  93 +++
 .../reverse/filters/IncludeFilterTest.java      |  34 +
 .../reverse/filters/PatternFilterTest.java      |  78 ++
 .../dbsync/reverse/filters/TableFilterTest.java |  91 ++
 .../dbsync/reverse/mapper/DbTypeTest.java       |  87 ++
 .../apache/cayenne/dbsync/unit/DbSyncCase.java  |  57 ++
 .../cayenne/dbsync/unit/DbSyncCaseModule.java   |  38 +
 .../unit/DbSyncServerRuntimeProvider.java       |  50 ++
 .../cayenne-relationship-optimisation.xml       |   4 +
 .../reverse/relationship-optimisation.map.xml   |  43 +
 .../org/apache/cayenne/access/DbLoader.java     | 832 -------------------
 .../apache/cayenne/access/DbLoaderDelegate.java |  58 --
 .../access/loader/DbAttributesBaseLoader.java   | 107 ---
 .../access/loader/DbAttributesLoader.java       |  43 -
 .../loader/DbAttributesPerSchemaLoader.java     | 130 ---
 .../access/loader/DbLoaderConfiguration.java    | 150 ----
 .../cayenne/access/loader/DbTableLoader.java    | 196 -----
 .../access/loader/DefaultDbLoaderDelegate.java  |  60 --
 .../access/loader/LoggingDbLoaderDelegate.java  |  76 --
 .../loader/ManyToManyCandidateEntity.java       | 142 ----
 .../cayenne/access/loader/NameFilter.java       |  27 -
 .../access/loader/NamePatternMatcher.java       | 225 -----
 .../access/loader/filters/CatalogFilter.java    |  62 --
 .../access/loader/filters/FiltersConfig.java    |  81 --
 .../loader/filters/IncludeTableFilter.java      |  71 --
 .../filters/LegacyFilterConfigBridge.java       | 150 ----
 .../access/loader/filters/PatternFilter.java    | 169 ----
 .../access/loader/filters/SchemaFilter.java     |  44 -
 .../access/loader/filters/TableFilter.java      | 133 ---
 .../cayenne/access/loader/mapper/DbType.java    | 194 -----
 .../mapper/DefaultJdbc2JavaTypeMapper.java      | 282 -------
 .../loader/mapper/Jdbc2JavaTypeMapper.java      |  33 -
 .../org/apache/cayenne/dba/AutoAdapter.java     |   6 -
 .../java/org/apache/cayenne/dba/DbAdapter.java  |   6 -
 .../org/apache/cayenne/dba/JdbcAdapter.java     |  14 +-
 .../apache/cayenne/dba/PerAdapterProvider.java  |  46 +
 .../org/apache/cayenne/dba/db2/DB2Adapter.java  |  13 +-
 .../cayenne/dba/db2/DB2MergerFactory.java       |  49 --
 .../apache/cayenne/dba/derby/DerbyAdapter.java  |  36 +-
 .../cayenne/dba/derby/DerbyMergerFactory.java   |  86 --
 .../cayenne/dba/firebird/FirebirdAdapter.java   |   6 +-
 .../dba/firebird/FirebirdMergerFactory.java     |  91 --
 .../org/apache/cayenne/dba/h2/H2Adapter.java    |   6 -
 .../apache/cayenne/dba/h2/H2MergerFactory.java  |  83 --
 .../cayenne/dba/hsqldb/HSQLDBAdapter.java       |   7 -
 .../cayenne/dba/hsqldb/HSQLMergerFactory.java   |  83 --
 .../cayenne/dba/ingres/IngresAdapter.java       |   6 -
 .../cayenne/dba/ingres/IngresMergerFactory.java | 226 -----
 .../apache/cayenne/dba/mysql/MySQLAdapter.java  |  26 +-
 .../cayenne/dba/mysql/MySQLMergerFactory.java   | 157 ----
 .../cayenne/dba/openbase/OpenBaseAdapter.java   | 469 +++++------
 .../dba/openbase/OpenBaseMergerFactory.java     | 144 ----
 .../cayenne/dba/oracle/OracleAdapter.java       |  25 +-
 .../cayenne/dba/oracle/OracleMergerFactory.java | 112 ---
 .../cayenne/dba/postgres/PostgresAdapter.java   |   6 -
 .../dba/postgres/PostgresMergerFactory.java     |  50 --
 .../cayenne/dba/sqlserver/SQLServerAdapter.java |   6 -
 .../dba/sqlserver/SQLServerMergerFactory.java   | 114 ---
 .../cayenne/dba/sybase/SybaseAdapter.java       |  14 +-
 .../cayenne/dba/sybase/SybaseMergerFactory.java | 167 ----
 .../cayenne/dbimport/FiltersConfigBuilder.java  | 383 ---------
 .../apache/cayenne/merge/AbstractToDbToken.java | 126 ---
 .../cayenne/merge/AbstractToModelToken.java     | 122 ---
 .../org/apache/cayenne/merge/AddColumnToDb.java |  64 --
 .../apache/cayenne/merge/AddColumnToModel.java  |  55 --
 .../cayenne/merge/AddRelationshipToDb.java      |  86 --
 .../cayenne/merge/AddRelationshipToModel.java   |  95 ---
 .../apache/cayenne/merge/CreateTableToDb.java   |  65 --
 .../cayenne/merge/CreateTableToModel.java       | 102 ---
 .../java/org/apache/cayenne/merge/DbMerger.java | 405 ---------
 .../apache/cayenne/merge/DbMergerConfig.java    |  63 --
 .../merge/DefaultModelMergeDelegate.java        |  89 --
 .../merge/DefaultValueForNullProvider.java      |  62 --
 .../apache/cayenne/merge/DropColumnToDb.java    |  51 --
 .../apache/cayenne/merge/DropColumnToModel.java |  74 --
 .../cayenne/merge/DropRelationshipToDb.java     |  71 --
 .../cayenne/merge/DropRelationshipToModel.java  |  50 --
 .../org/apache/cayenne/merge/DropTableToDb.java |  49 --
 .../apache/cayenne/merge/DropTableToModel.java  |  48 --
 .../apache/cayenne/merge/DummyReverseToken.java |  58 --
 .../merge/EmptyValueForNullProvider.java        |  40 -
 .../apache/cayenne/merge/MergeDirection.java    |  70 --
 .../org/apache/cayenne/merge/MergerContext.java | 118 ---
 .../org/apache/cayenne/merge/MergerFactory.java | 142 ----
 .../org/apache/cayenne/merge/MergerToken.java   |  51 --
 .../cayenne/merge/ModelMergeDelegate.java       |  65 --
 .../cayenne/merge/ProxyModelMergeDelegate.java  | 108 ---
 .../apache/cayenne/merge/SetAllowNullToDb.java  |  56 --
 .../cayenne/merge/SetAllowNullToModel.java      |  42 -
 .../apache/cayenne/merge/SetColumnTypeToDb.java | 119 ---
 .../cayenne/merge/SetColumnTypeToModel.java     | 101 ---
 .../apache/cayenne/merge/SetNotNullToDb.java    |  50 --
 .../apache/cayenne/merge/SetNotNullToModel.java |  42 -
 .../apache/cayenne/merge/SetPrimaryKeyToDb.java |  85 --
 .../cayenne/merge/SetPrimaryKeyToModel.java     |  77 --
 .../cayenne/merge/SetValueForNullToDb.java      |  46 -
 .../cayenne/merge/ValueForNullProvider.java     |  42 -
 .../apache/cayenne/util/EntityMergeSupport.java | 520 ------------
 .../org/apache/cayenne/access/DbLoaderIT.java   | 431 ----------
 .../cayenne/access/DbLoaderPartialIT.java       | 118 ---
 .../loader/ManyToManyCandidateEntityTest.java   | 113 ---
 .../loader/filters/FiltersConfigTest.java       |  93 ---
 .../loader/filters/IncludeFilterTest.java       |  34 -
 .../loader/filters/PatternFilterTest.java       |  78 --
 .../access/loader/filters/TableFilterTest.java  |  91 --
 .../access/loader/mapper/DbTypeTest.java        |  87 --
 .../cayenne/dba/PerAdapterProviderTest.java     |  83 ++
 .../dbimport/FiltersConfigBuilderTest.java      | 382 ---------
 .../cayenne/merge/AddColumnToModelIT.java       |  98 ---
 .../cayenne/merge/CreateTableToModelIT.java     |  97 ---
 .../org/apache/cayenne/merge/DbMergerTest.java  | 261 ------
 .../cayenne/merge/DropColumnToModelIT.java      | 235 ------
 .../merge/DropRelationshipToModelIT.java        | 188 -----
 .../cayenne/merge/DropTableToModelIT.java       |  94 ---
 .../org/apache/cayenne/merge/MergeCase.java     | 215 -----
 .../apache/cayenne/merge/MergerFactoryIT.java   | 310 -------
 .../cayenne/merge/SetAllowNullToDbIT.java       |  66 --
 .../apache/cayenne/merge/SetNotNullToDbIT.java  |  62 --
 .../cayenne/merge/SetPrimaryKeyToDbIT.java      |  58 --
 .../apache/cayenne/merge/TokensReversTest.java  |  88 --
 .../cayenne/merge/TokensToModelExecution.java   |  83 --
 .../apache/cayenne/merge/ValueForNullIT.java    | 128 ---
 .../apache/cayenne/merge/builders/Builder.java  |  38 -
 .../cayenne/merge/builders/DataMapBuilder.java  | 128 ---
 .../merge/builders/DbAttributeBuilder.java      | 115 ---
 .../cayenne/merge/builders/DbEntityBuilder.java |  90 --
 .../merge/builders/DbRelationshipBuilder.java   |  85 --
 .../cayenne/merge/builders/DefaultBuilder.java  |  57 --
 .../merge/builders/ObjAttributeBuilder.java     |  67 --
 .../merge/builders/ObjEntityBuilder.java        |  98 ---
 .../cayenne/merge/builders/ObjectMother.java    |  70 --
 .../unit/di/server/ServerRuntimeProvider.java   |  13 +-
 .../cayenne/util/EntityMergeSupportIT.java      | 103 ---
 .../cayenne-relationship-optimisation.xml       |   4 -
 .../loader/relationship-optimisation.map.xml    |  43 -
 cayenne-tools/pom.xml                           |  14 +
 .../cayenne/gen/ClassGenerationAction.java      |  22 +-
 .../cayenne/tools/AntDataPortDelegate.java      |  10 +-
 .../CayenneGeneratorEntityFilterAction.java     |  10 +-
 .../cayenne/tools/CayenneGeneratorTask.java     |   2 +-
 .../apache/cayenne/tools/DbGeneratorTask.java   |   9 +-
 .../apache/cayenne/tools/DbImporterTask.java    |   9 +-
 .../tools/dbimport/DbImportConfiguration.java   |  27 +-
 .../dbimport/DbImportDbLoaderDelegate.java      |   2 +-
 .../cayenne/tools/dbimport/DbImportModule.java  |   4 +-
 .../tools/dbimport/DefaultDbImportAction.java   |  34 +-
 .../config/DefaultTypeMapperBuilder.java        |   6 +-
 .../cayenne/tools/NamePatternMatcherTest.java   |   8 +-
 .../tools/dbimport/DbImportModuleTest.java      |   3 +-
 .../dbimport/DefaultDbImportActionTest.java     |  40 +-
 docs/doc/src/main/resources/RELEASE-NOTES.txt   |   1 +
 .../java/org/apache/cayenne/modeler/Main.java   |  17 +-
 .../modeler/action/CreateObjEntityAction.java   |   6 +-
 .../modeler/action/DbEntitySyncAction.java      |   8 +-
 .../cayenne/modeler/action/MigrateAction.java   |  72 +-
 .../modeler/action/ObjEntitySyncAction.java     |  13 +-
 .../modeler/dialog/db/DbLoaderHelper.java       |  16 +-
 .../modeler/dialog/db/MergerOptions.java        |  67 +-
 .../db/MergerTokenSelectorController.java       |  40 +-
 .../dialog/db/MergerTokenTableModel.java        |   6 +-
 .../dialog/db/ModelerDbImportAction.java        |   6 +-
 .../dialog/db/ReverseEngineeringController.java |  17 +-
 .../dialog/objentity/EntitySyncController.java  |  15 +-
 .../cayenne/tools/CayenneGeneratorMojo.java     |   2 +-
 .../apache/cayenne/tools/DbGeneratorMojo.java   |   9 +-
 .../apache/cayenne/tools/DbImporterMojo.java    |  21 +-
 .../tools/DbImporterMojoConfigurationTest.java  |   8 +-
 pom.xml                                         |   1 +
 276 files changed, 14069 insertions(+), 13421 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/assembly/pom.xml
----------------------------------------------------------------------
diff --git a/assembly/pom.xml b/assembly/pom.xml
index 6b7f423..45c042e 100644
--- a/assembly/pom.xml
+++ b/assembly/pom.xml
@@ -50,6 +50,12 @@
 
 		<dependency>
 			<groupId>org.apache.cayenne</groupId>
+			<artifactId>cayenne-dbsync</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>org.apache.cayenne</groupId>
 			<artifactId>cayenne-tools</artifactId>
 			<version>${project.version}</version>
 		</dependency>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/assembly/src/main/resources/assemblies/assembly-generic.xml
----------------------------------------------------------------------
diff --git a/assembly/src/main/resources/assemblies/assembly-generic.xml b/assembly/src/main/resources/assemblies/assembly-generic.xml
index 4414219..5eb4a62 100644
--- a/assembly/src/main/resources/assemblies/assembly-generic.xml
+++ b/assembly/src/main/resources/assemblies/assembly-generic.xml
@@ -80,6 +80,7 @@
 				<include>org.apache.cayenne:cayenne-lifecycle</include>
 				<include>org.apache.cayenne:cayenne-project</include>
 				<include>org.apache.cayenne:cayenne-server</include>
+				<include>org.apache.cayenne:cayenne-dbsync</include>
 				<include>org.apache.cayenne:cayenne-tools</include>
 				<include>org.apache.cayenne:cayenne-dbcp2</include>
 				<include>org.apache.cayenne:cayenne-java8</include>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/assembly/src/main/resources/assemblies/assembly-mac.xml
----------------------------------------------------------------------
diff --git a/assembly/src/main/resources/assemblies/assembly-mac.xml b/assembly/src/main/resources/assemblies/assembly-mac.xml
index 05a4d1f..11e3546 100644
--- a/assembly/src/main/resources/assemblies/assembly-mac.xml
+++ b/assembly/src/main/resources/assemblies/assembly-mac.xml
@@ -80,6 +80,7 @@
 				<include>org.apache.cayenne:cayenne-lifecycle</include>
 				<include>org.apache.cayenne:cayenne-project</include>
 				<include>org.apache.cayenne:cayenne-server</include>
+				<include>org.apache.cayenne:cayenne-dbsync</include>
 				<include>org.apache.cayenne:cayenne-tools</include>
 				<include>org.apache.cayenne:cayenne-dbcp2</include>
 				<include>org.apache.cayenne:cayenne-java8</include>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/assembly/src/main/resources/assemblies/assembly-windows.xml
----------------------------------------------------------------------
diff --git a/assembly/src/main/resources/assemblies/assembly-windows.xml b/assembly/src/main/resources/assemblies/assembly-windows.xml
index 7401a42..efa451d 100644
--- a/assembly/src/main/resources/assemblies/assembly-windows.xml
+++ b/assembly/src/main/resources/assemblies/assembly-windows.xml
@@ -80,6 +80,7 @@
 				<include>org.apache.cayenne:cayenne-lifecycle</include>
 				<include>org.apache.cayenne:cayenne-project</include>
 				<include>org.apache.cayenne:cayenne-server</include>
+				<include>org.apache.cayenne:cayenne-dbsync</include>
 				<include>org.apache.cayenne:cayenne-tools</include>
 				<include>org.apache.cayenne:cayenne-dbcp2</include>
 				<include>org.apache.cayenne:cayenne-java8</include>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/pom.xml b/cayenne-dbsync/pom.xml
new file mode 100644
index 0000000..5cc1a63
--- /dev/null
+++ b/cayenne-dbsync/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+	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.   
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+	<parent>
+		<artifactId>cayenne-parent</artifactId>
+		<groupId>org.apache.cayenne</groupId>
+		<version>4.0.M4-SNAPSHOT</version>
+	</parent>
+
+	<modelVersion>4.0.0</modelVersion>
+
+	<artifactId>cayenne-dbsync</artifactId>
+	<packaging>jar</packaging>
+	<name>Cayenne Database Synchronization Tools</name>
+	<dependencies>
+		<dependency>
+			<groupId>org.apache.cayenne</groupId>
+			<artifactId>cayenne-server</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.mockito</groupId>
+			<artifactId>mockito-all</artifactId>
+			<scope>test</scope>
+		</dependency>
+        <dependency>
+            <groupId>xmlunit</groupId>
+            <artifactId>xmlunit</artifactId>
+            <scope>test</scope>
+        </dependency>
+		<dependency>
+			<groupId>org.apache.cayenne</groupId>
+			<artifactId>cayenne-server</artifactId>
+			<version>${project.version}</version>
+			<scope>test</scope>
+			<type>test-jar</type>
+		</dependency>
+
+		<dependency>
+			<groupId>org.apache.cayenne.build-tools</groupId>
+			<artifactId>cayenne-test-utilities</artifactId>
+			<version>${project.version}</version>
+			<scope>test</scope>
+		</dependency>
+	</dependencies>
+	
+	<build>
+		<plugins>
+			<!-- This ensures LICESNE and NOTICE inclusion in all jars -->
+            <plugin>
+                <artifactId>maven-remote-resources-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>process</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+			<plugin>
+				<artifactId>maven-jar-plugin</artifactId>
+				<!-- include OSGi stuff -->
+				<configuration>
+					<archive>
+						<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+					</archive>
+				</configuration>
+				<!-- share tests with downstream modules -->
+				<executions>
+					<execution>
+						<goals>
+							<goal>test-jar</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>bundle-manifest</id>
+						<phase>process-classes</phase>
+						<goals>
+							<goal>manifest</goal>
+						</goals>
+						<!-- TODO: export package filters. -->
+					</execution>
+				</executions>
+			</plugin>
+        </plugins>
+	</build>
+</project>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/CayenneDbSyncModule.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/CayenneDbSyncModule.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/CayenneDbSyncModule.java
new file mode 100644
index 0000000..4fa43b2
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/CayenneDbSyncModule.java
@@ -0,0 +1,82 @@
+package org.apache.cayenne.dbsync;
+
+import org.apache.cayenne.dba.db2.DB2Adapter;
+import org.apache.cayenne.dba.derby.DerbyAdapter;
+import org.apache.cayenne.dba.firebird.FirebirdAdapter;
+import org.apache.cayenne.dba.h2.H2Adapter;
+import org.apache.cayenne.dba.hsqldb.HSQLDBAdapter;
+import org.apache.cayenne.dba.ingres.IngresAdapter;
+import org.apache.cayenne.dba.mysql.MySQLAdapter;
+import org.apache.cayenne.dba.openbase.OpenBaseAdapter;
+import org.apache.cayenne.dba.oracle.Oracle8Adapter;
+import org.apache.cayenne.dba.oracle.OracleAdapter;
+import org.apache.cayenne.dba.postgres.PostgresAdapter;
+import org.apache.cayenne.dba.sqlserver.SQLServerAdapter;
+import org.apache.cayenne.dba.sybase.SybaseAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
+import org.apache.cayenne.dbsync.merge.factory.DB2MergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.DefaultMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.DerbyMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.FirebirdMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.H2MergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.HSQLMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.IngresMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.MySQLMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.OpenBaseMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.OracleMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.PostgresMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.SQLServerMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.SybaseMergerTokenFactory;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.Module;
+
+/**
+ * @since 4.0
+ */
+public class CayenneDbSyncModule implements Module {
+
+    /**
+     * A DI container key for the Map&lt;String, String&gt; storing properties
+     * used by built-in Cayenne service.
+     */
+    public static final String MERGER_FACTORIES_MAP = "cayenne.dbsync.mergerfactories";
+
+    @Override
+    public void configure(Binder binder) {
+
+        // TODO: explicit binding before inserting into a map will be uneeded soon
+        binder.bind(DB2MergerTokenFactory.class).to(DB2MergerTokenFactory.class);
+        binder.bind(DerbyMergerTokenFactory.class).to(DerbyMergerTokenFactory.class);
+        binder.bind(FirebirdMergerTokenFactory.class).to(FirebirdMergerTokenFactory.class);
+        binder.bind(H2MergerTokenFactory.class).to(H2MergerTokenFactory.class);
+        binder.bind(HSQLMergerTokenFactory.class).to(HSQLMergerTokenFactory.class);
+        binder.bind(IngresMergerTokenFactory.class).to(IngresMergerTokenFactory.class);
+        binder.bind(MySQLMergerTokenFactory.class).to(MySQLMergerTokenFactory.class);
+        binder.bind(OpenBaseMergerTokenFactory.class).to(OpenBaseMergerTokenFactory.class);
+        binder.bind(OracleMergerTokenFactory.class).to(OracleMergerTokenFactory.class);
+        binder.bind(PostgresMergerTokenFactory.class).to(PostgresMergerTokenFactory.class);
+        binder.bind(SQLServerMergerTokenFactory.class).to(SQLServerMergerTokenFactory.class);
+        binder.bind(SybaseMergerTokenFactory.class).to(SybaseMergerTokenFactory.class);
+
+        // default and per adapter merger factories...
+        binder.bind(MergerTokenFactory.class).to(DefaultMergerTokenFactory.class);
+        binder.bindMap(MERGER_FACTORIES_MAP)
+                .put(DB2Adapter.class.getName(), DB2MergerTokenFactory.class)
+                .put(DerbyAdapter.class.getName(), DerbyMergerTokenFactory.class)
+                .put(FirebirdAdapter.class.getName(), FirebirdMergerTokenFactory.class)
+                .put(H2Adapter.class.getName(), H2MergerTokenFactory.class)
+                .put(HSQLDBAdapter.class.getName(), HSQLMergerTokenFactory.class)
+                .put(IngresAdapter.class.getName(), IngresMergerTokenFactory.class)
+                .put(MySQLAdapter.class.getName(), MySQLMergerTokenFactory.class)
+                .put(OpenBaseAdapter.class.getName(), OpenBaseMergerTokenFactory.class)
+                .put(OracleAdapter.class.getName(), OracleMergerTokenFactory.class)
+                .put(Oracle8Adapter.class.getName(), OracleMergerTokenFactory.class)
+                .put(PostgresAdapter.class.getName(), PostgresMergerTokenFactory.class)
+                .put(SQLServerAdapter.class.getName(), SQLServerMergerTokenFactory.class)
+                .put(SybaseAdapter.class.getName(), SybaseMergerTokenFactory.class);
+
+        binder.bind(MergerTokenFactoryProvider.class).to(MergerTokenFactoryProvider.class);
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AbstractToDbToken.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AbstractToDbToken.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AbstractToDbToken.java
new file mode 100644
index 0000000..1cc3092
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AbstractToDbToken.java
@@ -0,0 +1,126 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.log.JdbcEventLogger;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.validation.SimpleValidationFailure;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.List;
+
+/**
+ * Common abstract superclass for all {@link MergerToken}s going from the model
+ * to the database.
+ */
+public abstract class AbstractToDbToken implements MergerToken, Comparable<MergerToken> {
+
+	private final String tokenName;
+
+	protected AbstractToDbToken(String tokenName) {
+		this.tokenName = tokenName;
+	}
+
+	@Override
+	public final String getTokenName() {
+		return tokenName;
+	}
+
+	@Override
+	public final MergeDirection getDirection() {
+		return MergeDirection.TO_DB;
+	}
+
+	@Override
+	public void execute(MergerContext mergerContext) {
+		for (String sql : createSql(mergerContext.getDataNode().getAdapter())) {
+			executeSql(mergerContext, sql);
+		}
+	}
+
+	protected void executeSql(MergerContext mergerContext, String sql) {
+		JdbcEventLogger logger = mergerContext.getDataNode().getJdbcEventLogger();
+		logger.log(sql);
+
+		try (Connection conn = mergerContext.getDataNode().getDataSource().getConnection();) {
+
+			try (Statement st = conn.createStatement();) {
+				st.execute(sql);
+			}
+		} catch (SQLException e) {
+			mergerContext.getValidationResult().addFailure(new SimpleValidationFailure(sql, e.getMessage()));
+			logger.logQueryError(e);
+		}
+	}
+
+	@Override
+	public String toString() {
+		return getTokenName() + ' ' + getTokenValue() + ' ' + getDirection();
+	}
+
+	public abstract List<String> createSql(DbAdapter adapter);
+
+	abstract static class Entity extends AbstractToDbToken {
+
+		private DbEntity entity;
+
+		public Entity(String tokenName, DbEntity entity) {
+			super(tokenName);
+			this.entity = entity;
+		}
+
+		public DbEntity getEntity() {
+			return entity;
+		}
+
+		public String getTokenValue() {
+			return getEntity().getName();
+		}
+
+		public int compareTo(MergerToken o) {
+			// default order as tokens are created
+			return 0;
+		}
+
+	}
+
+	abstract static class EntityAndColumn extends Entity {
+
+		private DbAttribute column;
+
+		public EntityAndColumn(String tokenName, DbEntity entity, DbAttribute column) {
+			super(tokenName, entity);
+			this.column = column;
+		}
+
+		public DbAttribute getColumn() {
+			return column;
+		}
+
+		@Override
+		public String getTokenValue() {
+			return getEntity().getName() + "." + getColumn().getName();
+		}
+
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AbstractToModelToken.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AbstractToModelToken.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AbstractToModelToken.java
new file mode 100644
index 0000000..0c4b22e
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AbstractToModelToken.java
@@ -0,0 +1,122 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+
+/**
+ * Common abstract superclass for all {@link MergerToken}s going from the database to the
+ * model.
+ * 
+ */
+public abstract class AbstractToModelToken implements MergerToken {
+
+    private final String tokenName;
+
+    protected AbstractToModelToken(String tokenName) {
+        this.tokenName = tokenName;
+    }
+
+    @Override
+    public final String getTokenName() {
+        return tokenName;
+    }
+
+    public final MergeDirection getDirection() {
+        return MergeDirection.TO_MODEL;
+    }
+
+    protected static void remove(ModelMergeDelegate mergerContext, DbRelationship rel, boolean reverse) {
+        if (rel == null) {
+            return;
+        }
+        if (reverse) {
+            remove(mergerContext, rel.getReverseRelationship(), false);
+        }
+
+        DbEntity dbEntity = rel.getSourceEntity();
+        for (ObjEntity objEntity : dbEntity.mappedObjEntities()) {
+            remove(mergerContext, objEntity.getRelationshipForDbRelationship(rel), true);
+        }
+        
+        rel.getSourceEntity().removeRelationship(rel.getName());
+        mergerContext.dbRelationshipRemoved(rel);
+    }
+
+    protected static void remove(ModelMergeDelegate mergerContext, ObjRelationship rel, boolean reverse) {
+        if (rel == null) {
+            return;
+        }
+        if (reverse) {
+            remove(mergerContext, rel.getReverseRelationship(), false);
+        }
+        rel.getSourceEntity().removeRelationship(rel.getName());
+        mergerContext.objRelationshipRemoved(rel);
+    }
+
+    @Override
+    public String toString() {
+        return getTokenName() + ' ' + getTokenValue() + ' ' + getDirection();
+    }
+    
+    abstract static class Entity extends AbstractToModelToken {
+        
+        private final DbEntity entity;
+
+        protected Entity(String tokenName, DbEntity entity) {
+            super(tokenName);
+            this.entity = entity;
+        }
+
+        public DbEntity getEntity() {
+            return entity;
+        }
+        
+        public String getTokenValue() {
+            return getEntity().getName();
+        }
+        
+    }
+    
+    abstract static class EntityAndColumn extends Entity {
+        
+        private final DbAttribute column;
+        
+        protected EntityAndColumn(String tokenName, DbEntity entity, DbAttribute column) {
+            super(tokenName, entity);
+            this.column = column;
+        }
+
+        public DbAttribute getColumn() {
+            return column;
+        }
+
+        @Override
+        public String getTokenValue() {
+            return getEntity().getName() + "." + getColumn().getName();
+        }
+        
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddColumnToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddColumnToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddColumnToDb.java
new file mode 100644
index 0000000..977128c
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddColumnToDb.java
@@ -0,0 +1,65 @@
+/*
+ * 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.dbsync.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.JdbcAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+public class AddColumnToDb extends AbstractToDbToken.EntityAndColumn {
+
+    public AddColumnToDb(DbEntity entity, DbAttribute column) {
+        super("Add Column", entity, column);
+    }
+
+    /**
+     * append the part of the token before the actual column data type
+     */
+    protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+
+        sqlBuffer.append("ALTER TABLE ");
+        sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+        sqlBuffer.append(" ADD COLUMN ");
+        sqlBuffer.append(context.quotedName(getColumn()));
+        sqlBuffer.append(" ");
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        StringBuffer sqlBuffer = new StringBuffer();
+        QuotingStrategy context = adapter.getQuotingStrategy();
+        appendPrefix(sqlBuffer, context);
+
+        sqlBuffer.append(JdbcAdapter.getType(adapter, getColumn()));
+        sqlBuffer.append(JdbcAdapter.sizeAndPrecision(adapter, getColumn()));
+
+        return Collections.singletonList(sqlBuffer.toString());
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createDropColumnToModel(getEntity(), getColumn());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddColumnToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddColumnToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddColumnToModel.java
new file mode 100644
index 0000000..30390ca
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddColumnToModel.java
@@ -0,0 +1,55 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * A {@link MergerToken} to add a {@link DbAttribute} to a {@link DbEntity}. The
+ * {@link EntityMergeSupport} will be used to update the mapped {@link ObjEntity}
+ * 
+ */
+public class AddColumnToModel extends AbstractToModelToken.EntityAndColumn {
+
+    public AddColumnToModel(DbEntity entity, DbAttribute column) {
+        super("Add Column", entity, column);
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createDropColumnToDb(getEntity(), getColumn());
+    }
+
+    public void execute(MergerContext mergerContext) {
+        getEntity().addAttribute(getColumn());
+
+        // TODO: use EntityMergeSupport from DbImportConfiguration... otherwise we are ignoring a bunch of
+        // important settings
+
+        EntityMergeSupport entityMergeSupport =  new EntityMergeSupport(mergerContext.getDataMap());
+        for(ObjEntity e : getEntity().mappedObjEntities()) {
+            entityMergeSupport.synchronizeOnDbAttributeAdded(e, getColumn());
+        }
+
+        mergerContext.getModelMergeDelegate().dbAttributeAdded(getColumn());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddRelationshipToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddRelationshipToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddRelationshipToDb.java
new file mode 100644
index 0000000..5c851a6
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddRelationshipToDb.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.dbsync.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.access.DbGenerator;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+public class AddRelationshipToDb extends AbstractToDbToken.Entity {
+
+    private DbRelationship rel;
+
+    public AddRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        super("Add foreign key", entity);
+        this.rel = rel;
+    }
+
+    /**
+     * @see DbGenerator#createConstraintsQueries(org.apache.cayenne.map.DbEntity)
+     */
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        // TODO: skip FK to a different DB
+
+        if (this.shouldGenerateFkConstraint()) {
+            String fksql = adapter.createFkConstraint(rel);
+            if (fksql != null) {
+                return Collections.singletonList(fksql);
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    public boolean shouldGenerateFkConstraint() {
+        return !rel.isToMany()
+                && rel.isToPK() // TODO it is not necessary primary key it can be unique index
+                && !rel.isToDependentPK();
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createDropRelationshipToModel(getEntity(), rel);
+    }
+
+    @Override
+    public String getTokenValue() {
+        if (this.shouldGenerateFkConstraint()) {
+            return rel.getSourceEntity().getName() + "->" + rel.getTargetEntityName();
+        } else {
+            return "Skip. No sql representation.";
+        }
+    }
+    
+    public DbRelationship getRelationship() {
+        return rel;
+    }
+    
+    @Override
+    public int compareTo(MergerToken o) {
+        // add all AddRelationshipToDb to the end.
+        if (o instanceof AddRelationshipToDb) {
+            return super.compareTo(o);
+        }
+        return 1;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddRelationshipToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddRelationshipToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddRelationshipToModel.java
new file mode 100644
index 0000000..f516a3e
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/AddRelationshipToModel.java
@@ -0,0 +1,95 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjEntity;
+
+public class AddRelationshipToModel extends AbstractToModelToken.Entity {
+
+    public static final String COMMA_SEPARATOR = ", ";
+    public static final int COMMA_SEPARATOR_LENGTH = COMMA_SEPARATOR.length();
+    private DbRelationship rel;
+
+    public AddRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        super("Add Relationship", entity);
+        this.rel = rel;
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createDropRelationshipToDb(getEntity(), rel);
+    }
+
+    public void execute(MergerContext mergerContext) {
+        getEntity().addRelationship(rel);
+        // TODO: add reverse relationship as well if it does not exist
+
+        // TODO: use EntityMergeSupport from DbImportConfiguration... otherwise we are ignoring a bunch of
+        // important settings
+
+        EntityMergeSupport entityMergeSupport =  new EntityMergeSupport(mergerContext.getDataMap());
+        for(ObjEntity e : getEntity().mappedObjEntities()) {
+            entityMergeSupport.synchronizeOnDbRelationshipAdded(e, rel);
+        }
+
+        mergerContext.getModelMergeDelegate().dbRelationshipAdded(rel);
+    }
+
+    @Override
+    public String getTokenValue() {
+        String attributes = "";
+        if (rel.getJoins().size() == 1) {
+            attributes = rel.getJoins().get(0).getTargetName();
+        } else {
+            for (DbJoin dbJoin : rel.getJoins()) {
+                attributes += dbJoin.getTargetName() + COMMA_SEPARATOR;
+            }
+
+            attributes = "{" + attributes.substring(0, attributes.length() - COMMA_SEPARATOR_LENGTH) + "}";
+        }
+
+        return rel.getName() + " " + rel.getSourceEntity().getName() + "->" + rel.getTargetEntityName() + "." + attributes;
+    }
+
+
+    public static String getTokenValue(DbRelationship rel) {
+        String attributes = "";
+        if (rel.getJoins().size() == 1) {
+            attributes = rel.getJoins().get(0).getTargetName();
+        } else {
+            for (DbJoin dbJoin : rel.getJoins()) {
+                attributes += dbJoin.getTargetName() + COMMA_SEPARATOR;
+            }
+
+            attributes = "{" + attributes.substring(0, attributes.length() - COMMA_SEPARATOR_LENGTH) + "}";
+        }
+
+        return rel.getName() + " " + rel.getSourceEntity().getName() + "->" + rel.getTargetEntityName() + "." + attributes;
+    }
+
+    public DbRelationship getRelationship() {
+        return rel;
+    }
+
+
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToDb.java
new file mode 100644
index 0000000..9584aaf
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToDb.java
@@ -0,0 +1,66 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.validation.SimpleValidationFailure;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class CreateTableToDb extends AbstractToDbToken.Entity {
+
+    public CreateTableToDb(DbEntity entity) {
+        super("Create Table", entity);
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        List<String> sqls = new ArrayList<String>();
+        sqls.addAll(adapter.getPkGenerator().createAutoPkStatements(
+                Collections.singletonList(getEntity())));
+        sqls.add(adapter.createTable(getEntity()));
+        return sqls;
+    }
+
+    @Override
+    public void execute(MergerContext mergerContext) {
+        try {
+            DataNode node = mergerContext.getDataNode();
+            DbAdapter adapter = node.getAdapter();
+            adapter.getPkGenerator().createAutoPk(
+                    node,
+                    Collections.singletonList(getEntity()));
+            executeSql(mergerContext, adapter.createTable(getEntity()));
+        }
+        catch (Exception e) {
+            mergerContext.getValidationResult().addFailure(
+                    new SimpleValidationFailure(this, e.getMessage()));
+        }
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createDropTableToModel(getEntity());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToModel.java
new file mode 100644
index 0000000..69e18b3
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/CreateTableToModel.java
@@ -0,0 +1,102 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.naming.NameConverter;
+
+/**
+ * A {@link MergerToken} to add a {@link DbEntity} to a {@link DataMap}
+ * 
+ */
+public class CreateTableToModel extends AbstractToModelToken.Entity {
+
+    /**
+     * className if {@link ObjEntity} should be generated with a
+     *  special class name.
+     * Setting this to <code>null</code>, because by default class name should be generated 
+     */
+    private String objEntityClassName = null; //CayenneDataObject.class.getName();
+
+    public CreateTableToModel(DbEntity entity) {
+        super("Create Table", entity);
+    }
+
+    /**
+     * Set the {@link ObjEntity} className if {@link ObjEntity} should be generated with a
+     * special class name. Set to null if the {@link ObjEntity} should be created with a
+     * name based on {@link DataMap#getDefaultPackage()} and {@link ObjEntity#getName()}
+     * <p>
+     * The default value is <code>null</code>
+     */
+    public void setObjEntityClassName(String n) {
+        objEntityClassName = n;
+    }
+
+    public void execute(MergerContext mergerContext) {
+        DataMap map = mergerContext.getDataMap();
+        map.addDbEntity(getEntity());
+
+        // create a ObjEntity
+        String objEntityName = NameConverter.underscoredToJava(getEntity().getName(), true);
+        // this loop will terminate even if no valid name is found
+        // to prevent loader from looping forever (though such case is very unlikely)
+        String baseName = objEntityName;
+        for (int i = 1; i < 1000 && map.getObjEntity(objEntityName) != null; i++) {
+            objEntityName = baseName + i;
+        }
+
+        ObjEntity objEntity = new ObjEntity(objEntityName);
+        objEntity.setDbEntity(getEntity());
+
+        // try to find a class name for the ObjEntity
+        String className = objEntityClassName;
+        if (className == null) {
+            // we should generate a className based on the objEntityName
+            className = map.getNameWithDefaultPackage(objEntityName);
+        }
+
+        objEntity.setClassName(className);
+        objEntity.setSuperClassName(map.getDefaultSuperclass());
+        
+        if (map.isClientSupported()) {
+            objEntity.setClientClassName(map.getNameWithDefaultClientPackage(objEntity.getName()));
+            objEntity.setClientSuperClassName(map.getDefaultClientSuperclass());
+        }
+        
+        map.addObjEntity(objEntity);
+
+        // presumably there are no other ObjEntities pointing to this DbEntity, so syncing just this one...
+
+        // TODO: use EntityMergeSupport from DbImportConfiguration... otherwise we are ignoring a bunch of
+        // important settings
+        new EntityMergeSupport(map).synchronizeWithDbEntity(objEntity);
+
+        mergerContext.getModelMergeDelegate().dbEntityAdded(getEntity());
+        mergerContext.getModelMergeDelegate().objEntityAdded(objEntity);
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createDropTableToDb(getEntity());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DbMerger.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DbMerger.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DbMerger.java
new file mode 100644
index 0000000..f7d4346
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DbMerger.java
@@ -0,0 +1,405 @@
+/*
+ * 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.dbsync.merge;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.dbsync.reverse.DbLoader;
+import org.apache.cayenne.dbsync.reverse.DbLoaderConfiguration;
+import org.apache.cayenne.dbsync.reverse.LoggingDbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.map.Attribute;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.DetectedDbEntity;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Traverse a {@link DataNode} and a {@link DataMap} and create a group of
+ * {@link MergerToken}s to alter the {@link DataNode} data store to match the
+ * {@link DataMap}.
+ * 
+ */
+public class DbMerger {
+
+	private static final Log LOGGER = LogFactory.getLog(DbMerger.class);
+
+	private final MergerTokenFactory factory;
+
+	private final ValueForNullProvider valueForNull;
+
+	public DbMerger(MergerTokenFactory factory) {
+		this(factory, null);
+	}
+
+	public DbMerger(MergerTokenFactory factory, ValueForNullProvider valueForNull) {
+		this.factory = factory;
+		this.valueForNull = valueForNull == null ? new EmptyValueForNullProvider() : valueForNull;
+	}
+
+	/**
+	 * 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,
+			DbLoaderConfiguration config) {
+		return createMergeTokens(existingDataMap, loadDataMapFromDb(dataSource, adapter, config), 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(DataMap existing, DataMap loadedFomDb, DbLoaderConfiguration config) {
+
+		loadedFomDb.setQuotingSQLIdentifiers(existing.isQuotingSQLIdentifiers());
+
+		List<MergerToken> tokens = createMergeTokens(filter(existing, config.getFiltersConfig()),
+				loadedFomDb.getDbEntities(), config);
+
+		// sort. use a custom Comparator since only toDb tokens are comparable
+		// by now
+		Collections.sort(tokens, new Comparator<MergerToken>() {
+
+			public int compare(MergerToken o1, MergerToken o2) {
+				if (o1 instanceof AbstractToDbToken && o2 instanceof AbstractToDbToken) {
+
+					return ((AbstractToDbToken) o1).compareTo(o2);
+				}
+				return 0;
+			}
+		});
+
+		return tokens;
+	}
+
+	private Collection<DbEntity> filter(DataMap existing, FiltersConfig filtersConfig) {
+		Collection<DbEntity> existingFiltered = new LinkedList<DbEntity>();
+		for (DbEntity entity : existing.getDbEntities()) {
+			if (filtersConfig.tableFilter(entity.getCatalog(), entity.getSchema()).isIncludeTable(entity.getName()) != null) {
+				existingFiltered.add(entity);
+			}
+		}
+		return existingFiltered;
+	}
+
+	private DataMap loadDataMapFromDb(DataSource dataSource, DbAdapter adapter, DbLoaderConfiguration config) {
+		try (Connection conn = dataSource.getConnection();) {
+
+			return new DbLoader(conn, adapter, new LoggingDbLoaderDelegate(LOGGER)).load(config);
+		} catch (SQLException e) {
+			throw new CayenneRuntimeException("Can't doLoad dataMap from db.", e);
+		}
+	}
+
+	public List<MergerToken> createMergeTokens(Collection<DbEntity> existing, Collection<DbEntity> loadedFromDb,
+			DbLoaderConfiguration config) {
+		Collection<DbEntity> dbEntitiesToDrop = new LinkedList<DbEntity>(loadedFromDb);
+
+		List<MergerToken> tokens = new LinkedList<MergerToken>();
+		for (DbEntity dbEntity : existing) {
+			String tableName = dbEntity.getName();
+
+			// look for table
+			DbEntity detectedEntity = findDbEntity(loadedFromDb, tableName);
+			if (detectedEntity == null) {
+				tokens.add(factory.createCreateTableToDb(dbEntity));
+				// TODO: does this work properly with createReverse?
+				for (DbRelationship rel : dbEntity.getRelationships()) {
+					tokens.add(factory.createAddRelationshipToDb(dbEntity, rel));
+				}
+				continue;
+			}
+
+			dbEntitiesToDrop.remove(detectedEntity);
+
+			tokens.addAll(checkRelationshipsToDrop(dbEntity, detectedEntity));
+			if (!config.isSkipRelationshipsLoading()) {
+				tokens.addAll(checkRelationshipsToAdd(dbEntity, detectedEntity));
+			}
+			tokens.addAll(checkRows(dbEntity, detectedEntity));
+
+			if (!config.isSkipPrimaryKeyLoading()) {
+				MergerToken token = checkPrimaryKeyChange(dbEntity, detectedEntity);
+				if (token != null) {
+					tokens.add(token);
+				}
+			}
+		}
+
+		// drop table
+		// TODO: support drop table. currently, too many tables are marked for
+		// drop
+		for (DbEntity e : dbEntitiesToDrop) {
+			tokens.add(factory.createDropTableToDb(e));
+			for (DbRelationship relationship : e.getRelationships()) {
+				DbEntity detectedEntity = findDbEntity(existing, relationship.getTargetEntityName());
+				if (detectedEntity != null) {
+					tokens.add(factory.createDropRelationshipToDb(detectedEntity, relationship.getReverseRelationship()));
+				}
+			}
+		}
+
+		return tokens;
+	}
+
+	private List<MergerToken> checkRows(DbEntity existing, DbEntity loadedFromDb) {
+		List<MergerToken> tokens = new LinkedList<MergerToken>();
+
+		// columns to drop
+		for (DbAttribute detected : loadedFromDb.getAttributes()) {
+			if (findDbAttribute(existing, detected.getName()) == null) {
+				tokens.add(factory.createDropColumnToDb(existing, detected));
+			}
+		}
+
+		// columns to add or modify
+		for (DbAttribute attr : existing.getAttributes()) {
+			String columnName = attr.getName().toUpperCase();
+
+			DbAttribute detected = findDbAttribute(loadedFromDb, columnName);
+
+			if (detected == null) {
+				tokens.add(factory.createAddColumnToDb(existing, attr));
+				if (attr.isMandatory()) {
+					if (valueForNull.hasValueFor(existing, attr)) {
+						tokens.add(factory.createSetValueForNullToDb(existing, attr, valueForNull));
+					}
+					tokens.add(factory.createSetNotNullToDb(existing, attr));
+				}
+				continue;
+			}
+
+			// check for not null
+			if (attr.isMandatory() != detected.isMandatory()) {
+				if (attr.isMandatory()) {
+					if (valueForNull.hasValueFor(existing, attr)) {
+						tokens.add(factory.createSetValueForNullToDb(existing, attr, valueForNull));
+					}
+					tokens.add(factory.createSetNotNullToDb(existing, attr));
+				} else {
+					tokens.add(factory.createSetAllowNullToDb(existing, attr));
+				}
+			}
+
+			// TODO: check more types than char/varchar
+			// TODO: psql report VARCHAR for text column, not clob
+			switch (detected.getType()) {
+			case Types.VARCHAR:
+			case Types.CHAR:
+				if (attr.getMaxLength() != detected.getMaxLength()) {
+					tokens.add(factory.createSetColumnTypeToDb(existing, detected, attr));
+				}
+				break;
+			}
+		}
+
+		return tokens;
+	}
+
+	private List<MergerToken> checkRelationshipsToDrop(DbEntity dbEntity, DbEntity detectedEntity) {
+		List<MergerToken> tokens = new LinkedList<MergerToken>();
+
+		// relationships to drop
+		for (DbRelationship detected : detectedEntity.getRelationships()) {
+			if (findDbRelationship(dbEntity, detected) == null) {
+
+				// alter detected relationship to match entity and attribute
+				// names.
+				// (case sensitively)
+
+				DbEntity targetEntity = findDbEntity(dbEntity.getDataMap().getDbEntities(),
+						detected.getTargetEntityName());
+				if (targetEntity == null) {
+					continue;
+				}
+
+				detected.setSourceEntity(dbEntity);
+				detected.setTargetEntityName(targetEntity);
+
+				// manipulate the joins to match the DbAttributes in the model
+				for (DbJoin join : detected.getJoins()) {
+					DbAttribute sattr = findDbAttribute(dbEntity, join.getSourceName());
+					if (sattr != null) {
+						join.setSourceName(sattr.getName());
+					}
+					DbAttribute tattr = findDbAttribute(targetEntity, join.getTargetName());
+					if (tattr != null) {
+						join.setTargetName(tattr.getName());
+					}
+				}
+
+				MergerToken token = factory.createDropRelationshipToDb(dbEntity, detected);
+				if (detected.isToMany()) {
+					// default toModel as we can not do drop a toMany in the db.
+					// only
+					// toOne are represented using foreign key
+					token = token.createReverse(factory);
+				}
+				tokens.add(token);
+			}
+		}
+
+		return tokens;
+	}
+
+	private List<MergerToken> checkRelationshipsToAdd(DbEntity dbEntity, DbEntity detectedEntity) {
+
+		List<MergerToken> tokens = new LinkedList<MergerToken>();
+
+		for (DbRelationship rel : dbEntity.getRelationships()) {
+			if (findDbRelationship(detectedEntity, rel) == null) {
+				AddRelationshipToDb token = (AddRelationshipToDb) factory.createAddRelationshipToDb(dbEntity, rel);
+
+				if (token.shouldGenerateFkConstraint()) {
+					// TODO I guess we should add relationship always; in order
+					// to have ability
+					// TODO generate reverse relationship. If it doesn't have
+					// anything to execute it will be passed
+					// TODO through execution without any affect on db
+					tokens.add(token);
+				}
+			}
+		}
+
+		return tokens;
+	}
+
+	private MergerToken checkPrimaryKeyChange(DbEntity dbEntity, DbEntity detectedEntity) {
+		Collection<DbAttribute> primaryKeyOriginal = detectedEntity.getPrimaryKeys();
+		Collection<DbAttribute> primaryKeyNew = dbEntity.getPrimaryKeys();
+
+		String primaryKeyName = null;
+		if (detectedEntity instanceof DetectedDbEntity) {
+			primaryKeyName = ((DetectedDbEntity) detectedEntity).getPrimaryKeyName();
+		}
+
+		if (upperCaseEntityNames(primaryKeyOriginal).equals(upperCaseEntityNames(primaryKeyNew))) {
+			return null;
+		}
+
+		return factory.createSetPrimaryKeyToDb(dbEntity, primaryKeyOriginal, primaryKeyNew, primaryKeyName);
+	}
+
+	private Set<String> upperCaseEntityNames(Collection<? extends Attribute> attrs) {
+		Set<String> names = new HashSet<String>();
+		for (Attribute attr : attrs) {
+			names.add(attr.getName().toUpperCase());
+		}
+		return names;
+	}
+
+	/**
+	 * case insensitive search for a {@link DbEntity} in a {@link DataMap} by
+	 * name
+	 */
+	private DbEntity findDbEntity(Collection<DbEntity> dbEntities, String caseInsensitiveName) {
+		// TODO: create a Map with upper case keys?
+		for (DbEntity e : dbEntities) {
+			if (e.getName().equalsIgnoreCase(caseInsensitiveName)) {
+				return e;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * case insensitive search for a {@link DbAttribute} in a {@link DbEntity}
+	 * by name
+	 */
+	private DbAttribute findDbAttribute(DbEntity entity, String caseInsensitiveName) {
+		for (DbAttribute a : entity.getAttributes()) {
+			if (a.getName().equalsIgnoreCase(caseInsensitiveName)) {
+				return a;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * search for a {@link DbRelationship} like rel in the given
+	 * {@link DbEntity}
+	 */
+	private DbRelationship findDbRelationship(DbEntity entity, DbRelationship rel) {
+		for (DbRelationship candidate : entity.getRelationships()) {
+			if (equalDbJoinCollections(candidate.getJoins(), rel.getJoins())) {
+				return candidate;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Return true if the two unordered {@link Collection}s of {@link DbJoin}s
+	 * are equal. Entity and Attribute names are compared case insensitively.
+	 *
+	 * TODO complexity n^2; sort both collection and go through them to compare
+	 * = 2*n*log(n) + n
+	 */
+	private static boolean equalDbJoinCollections(Collection<DbJoin> j1s, Collection<DbJoin> j2s) {
+		if (j1s.size() != j2s.size()) {
+			return false;
+		}
+
+		for (DbJoin j1 : j1s) {
+			if (!havePair(j2s, j1)) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	private static boolean havePair(Collection<DbJoin> j2s, DbJoin j1) {
+		for (DbJoin j2 : j2s) {
+			if (!isNull(j1.getSource()) && !isNull(j1.getTarget()) && !isNull(j2.getSource())
+					&& !isNull(j2.getTarget())
+					&& j1.getSource().getEntity().getName().equalsIgnoreCase(j2.getSource().getEntity().getName())
+					&& j1.getTarget().getEntity().getName().equalsIgnoreCase(j2.getTarget().getEntity().getName())
+					&& j1.getSourceName().equalsIgnoreCase(j2.getSourceName())
+					&& j1.getTargetName().equalsIgnoreCase(j2.getTargetName())) {
+
+				return true;
+			}
+		}
+		return false;
+	}
+
+	private static boolean isNull(DbAttribute attribute) {
+		return attribute == null || attribute.getEntity() == null;
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DbMergerConfig.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DbMergerConfig.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DbMergerConfig.java
new file mode 100644
index 0000000..1cd80df
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DbMergerConfig.java
@@ -0,0 +1,63 @@
+/*
+ * 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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+
+/**
+ * @since 4.0
+ */
+public class DbMergerConfig {
+
+    private FiltersConfig filtersConfig;
+
+    private boolean skipRelationships;
+
+    private boolean skipPrimaryKey;
+
+    public DbMergerConfig(FiltersConfig filtersConfig, boolean skipRelationships, boolean skipPrimaryKey) {
+        this.filtersConfig = filtersConfig;
+        this.skipRelationships = skipRelationships;
+        this.skipPrimaryKey = skipPrimaryKey;
+    }
+
+    public void setSkipRelationships(boolean skipRelationships) {
+        this.skipRelationships = skipRelationships;
+    }
+
+    public boolean isSkipRelationships() {
+        return skipRelationships;
+    }
+
+    public void setSkipPrimaryKey(boolean skipPrimaryKey) {
+        this.skipPrimaryKey = skipPrimaryKey;
+    }
+
+    public boolean isSkipPrimaryKey() {
+        return skipPrimaryKey;
+    }
+
+    public FiltersConfig getFiltersConfig() {
+        return filtersConfig;
+    }
+
+    public void setFiltersConfig(FiltersConfig filtersConfig) {
+        this.filtersConfig = filtersConfig;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DefaultModelMergeDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DefaultModelMergeDelegate.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DefaultModelMergeDelegate.java
new file mode 100644
index 0000000..617aad2
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DefaultModelMergeDelegate.java
@@ -0,0 +1,89 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+
+/**
+ * A default noop implementation of {@link ModelMergeDelegate}.
+ */
+public class DefaultModelMergeDelegate implements ModelMergeDelegate {
+
+    @Override
+    public void dbAttributeAdded(DbAttribute att) {
+    }
+
+    @Override
+    public void dbAttributeModified(DbAttribute att) {
+    }
+
+    @Override
+    public void dbAttributeRemoved(DbAttribute att) {
+    }
+
+    @Override
+    public void dbEntityAdded(DbEntity ent) {
+    }
+
+    @Override
+    public void dbEntityRemoved(DbEntity ent) {
+    }
+
+    @Override
+    public void dbRelationshipAdded(DbRelationship rel) {
+    }
+
+    @Override
+    public void dbRelationshipRemoved(DbRelationship rel) {
+    }
+
+    @Override
+    public void objAttributeAdded(ObjAttribute att) {
+    }
+
+    @Override
+    public void objAttributeModified(ObjAttribute att) {
+    }
+
+    @Override
+    public void objAttributeRemoved(ObjAttribute att) {
+    }
+
+    @Override
+    public void objEntityAdded(ObjEntity ent) {
+    }
+
+    @Override
+    public void objEntityRemoved(ObjEntity ent) {
+    }
+
+    @Override
+    public void objRelationshipAdded(ObjRelationship rel) {
+    }
+
+    @Override
+    public void objRelationshipRemoved(ObjRelationship rel) {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DefaultValueForNullProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DefaultValueForNullProvider.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DefaultValueForNullProvider.java
new file mode 100644
index 0000000..9d4e0cd
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DefaultValueForNullProvider.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.dbsync.merge;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cayenne.access.jdbc.SQLParameterBinding;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+public class DefaultValueForNullProvider implements ValueForNullProvider {
+
+    private Map<String, SQLParameterBinding> values = new HashMap<>();
+
+    public void set(DbEntity entity, DbAttribute column, Object value, int type) {
+        values.put(createKey(entity, column), new SQLParameterBinding(value, type, column
+                .getAttributePrecision()));
+    }
+
+    protected SQLParameterBinding get(DbEntity entity, DbAttribute column) {
+        return values.get(createKey(entity, column));
+    }
+
+    public List<String> createSql(DbEntity entity, DbAttribute column) {
+        SQLParameterBinding value = get(entity, column);
+        if (value == null) {
+            return Collections.emptyList();
+        }
+
+        // TODO: change things so it is possible to use prepared statements here
+        return Collections.singletonList("UPDATE " + entity.getFullyQualifiedName()
+                + " SET " + column.getName() + "='" + value.getValue() + "' WHERE " + column.getName() + " IS NULL");
+    }
+
+    public boolean hasValueFor(DbEntity entity, DbAttribute column) {
+        return values.containsKey(createKey(entity, column));
+    }
+
+    private String createKey(DbEntity entity, DbAttribute attribute) {
+        return (entity.getFullyQualifiedName() + "." + attribute.getName()).toUpperCase();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropColumnToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropColumnToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropColumnToDb.java
new file mode 100644
index 0000000..19914f0
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropColumnToDb.java
@@ -0,0 +1,52 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+public class DropColumnToDb extends AbstractToDbToken.EntityAndColumn {
+
+    public DropColumnToDb(DbEntity entity, DbAttribute column) {
+        super("Drop Column", entity, column);
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        StringBuilder sqlBuffer = new StringBuilder();
+        QuotingStrategy context = adapter.getQuotingStrategy();
+        sqlBuffer.append("ALTER TABLE ");
+        sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+        sqlBuffer.append(" DROP COLUMN ");
+        sqlBuffer.append(context.quotedName(getColumn()));
+
+        return Collections.singletonList(sqlBuffer.toString());
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createAddColumnToModel(getEntity(), getColumn());
+    }
+
+}


[07/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DefaultJdbc2JavaTypeMapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DefaultJdbc2JavaTypeMapper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DefaultJdbc2JavaTypeMapper.java
deleted file mode 100644
index de9e02d..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/DefaultJdbc2JavaTypeMapper.java
+++ /dev/null
@@ -1,282 +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.loader.mapper;
-
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.util.Util;
-
-import java.io.Serializable;
-import java.math.BigInteger;
-import java.sql.Types;
-import java.util.Calendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.SortedSet;
-import java.util.TreeSet;
-
-/**
- * @since 4.0.
- */
-public class DefaultJdbc2JavaTypeMapper implements Jdbc2JavaTypeMapper {
-
-	// Never use "-1" or any other normal integer, since there
-	// is a big chance it is being reserved in java.sql.Types
-	public static final int NOT_DEFINED = Integer.MAX_VALUE;
-
-	// char constants for Java data types
-	public static final String JAVA_LONG = "java.lang.Long";
-	public static final String JAVA_BYTES = "byte[]";
-	public static final String JAVA_BOOLEAN = "java.lang.Boolean";
-	public static final String JAVA_STRING = "java.lang.String";
-	public static final String JAVA_SQLDATE = "java.sql.Date";
-	public static final String JAVA_UTILDATE = "java.util.Date";
-	public static final String JAVA_BIGDECIMAL = "java.math.BigDecimal";
-	public static final String JAVA_DOUBLE = "java.lang.Double";
-	public static final String JAVA_FLOAT = "java.lang.Float";
-	public static final String JAVA_INTEGER = "java.lang.Integer";
-	public static final String JAVA_SHORT = "java.lang.Short";
-	public static final String JAVA_BYTE = "java.lang.Byte";
-	public static final String JAVA_TIME = "java.sql.Time";
-	public static final String JAVA_TIMESTAMP = "java.sql.Timestamp";
-	public static final String JAVA_BLOB = "java.sql.Blob";
-
-	/**
-	 * Keys: java class names, Values: SQL int type definitions from
-	 * java.sql.Types
-	 */
-	private final Map<String, Integer> javaSqlEnum = new HashMap<>();
-
-	private final Map<DbType, String> mapping = new HashMap<>();
-	private final SortedSet<DbType> dbTypes = new TreeSet<>();
-
-	private Map<String, String> classToPrimitive;
-
-	{
-		add(Types.BIGINT, JAVA_LONG);
-		add(Types.BINARY, JAVA_BYTES);
-		add(Types.BIT, JAVA_BOOLEAN);
-		add(Types.BOOLEAN, JAVA_BOOLEAN);
-		add(Types.BLOB, JAVA_BYTES);
-		add(Types.CLOB, JAVA_STRING);
-		add(Types.NCLOB, JAVA_STRING);
-		add(Types.SQLXML, JAVA_STRING);
-		add(Types.CHAR, JAVA_STRING);
-		add(Types.NCHAR, JAVA_STRING);
-		add(Types.DATE, JAVA_UTILDATE);
-		add(Types.DECIMAL, JAVA_BIGDECIMAL);
-		add(Types.DOUBLE, JAVA_DOUBLE);
-		add(Types.FLOAT, JAVA_FLOAT);
-		add(Types.INTEGER, JAVA_INTEGER);
-		add(Types.LONGVARCHAR, JAVA_STRING);
-		add(Types.LONGNVARCHAR, JAVA_STRING);
-		add(Types.LONGVARBINARY, JAVA_BYTES);
-		add(Types.NUMERIC, JAVA_BIGDECIMAL);
-		add(Types.REAL, JAVA_FLOAT);
-		add(Types.SMALLINT, JAVA_SHORT);
-		add(Types.TINYINT, JAVA_SHORT);
-		add(Types.TIME, JAVA_UTILDATE);
-		add(Types.TIMESTAMP, JAVA_UTILDATE);
-		add(Types.VARBINARY, JAVA_BYTES);
-		add(Types.VARCHAR, JAVA_STRING);
-		add(Types.NVARCHAR, JAVA_STRING);
-
-		javaSqlEnum.put(JAVA_LONG, Types.BIGINT);
-		javaSqlEnum.put(JAVA_BYTES, Types.BINARY);
-		javaSqlEnum.put(JAVA_BOOLEAN, Types.BIT);
-		javaSqlEnum.put(JAVA_STRING, Types.VARCHAR);
-		javaSqlEnum.put(JAVA_SQLDATE, Types.DATE);
-		javaSqlEnum.put(JAVA_UTILDATE, Types.DATE);
-		javaSqlEnum.put(JAVA_TIMESTAMP, Types.TIMESTAMP);
-		javaSqlEnum.put(JAVA_BIGDECIMAL, Types.DECIMAL);
-		javaSqlEnum.put(JAVA_DOUBLE, Types.DOUBLE);
-		javaSqlEnum.put(JAVA_FLOAT, Types.FLOAT);
-		javaSqlEnum.put(JAVA_INTEGER, Types.INTEGER);
-		javaSqlEnum.put(JAVA_SHORT, Types.SMALLINT);
-		javaSqlEnum.put(JAVA_BYTE, Types.SMALLINT);
-		javaSqlEnum.put(JAVA_TIME, Types.TIME);
-		javaSqlEnum.put(JAVA_TIMESTAMP, Types.TIMESTAMP);
-
-		// add primitives
-		javaSqlEnum.put("byte", Types.TINYINT);
-		javaSqlEnum.put("int", Types.INTEGER);
-		javaSqlEnum.put("short", Types.SMALLINT);
-		javaSqlEnum.put("char", Types.CHAR);
-		javaSqlEnum.put("double", Types.DOUBLE);
-		javaSqlEnum.put("long", Types.BIGINT);
-		javaSqlEnum.put("float", Types.FLOAT);
-		javaSqlEnum.put("boolean", Types.BIT);
-
-		classToPrimitive = new HashMap<>();
-		classToPrimitive.put(Byte.class.getName(), "byte");
-		classToPrimitive.put(Long.class.getName(), "long");
-		classToPrimitive.put(Double.class.getName(), "double");
-		classToPrimitive.put(Boolean.class.getName(), "boolean");
-		classToPrimitive.put(Float.class.getName(), "float");
-		classToPrimitive.put(Short.class.getName(), "short");
-		classToPrimitive.put(Integer.class.getName(), "int");
-	}
-
-	private Boolean usePrimitives;
-
-	/**
-	 * Returns default java.sql.Types type by the Java type name.
-	 *
-	 * @param className
-	 *            Fully qualified Java Class name.
-	 * @return The SQL type or NOT_DEFINED if no type found.
-	 */
-	public int getJdbcTypeByJava(DbAttribute attribute, String className) {
-		if (className == null) {
-			return NOT_DEFINED;
-		}
-
-		Integer type = javaSqlEnum.get(className);
-		if (type != null) {
-			return type;
-		}
-
-		// try to load a Java class - some nonstandard mappings may work
-		try {
-			return getSqlTypeByJava(attribute, Util.getJavaClass(className));
-		} catch (Throwable th) {
-			return NOT_DEFINED;
-		}
-	}
-
-	public void add(int jdbcType, String java) {
-		add(new DbType(TypesMapping.getSqlNameByType(jdbcType)), java);
-	}
-
-	@Override
-	public void add(DbType type, String java) {
-		mapping.put(type, java);
-		dbTypes.add(type);
-	}
-
-	/**
-	 * Guesses a default JDBC type for the Java class.
-	 *
-	 * @since 1.1
-	 */
-	protected int getSqlTypeByJava(DbAttribute attribute, Class<?> javaClass) {
-		if (javaClass == null) {
-			return NOT_DEFINED;
-		}
-
-		// check standard mapping of class and superclasses
-		Class<?> aClass = javaClass;
-		while (aClass != null) {
-
-			String name;
-
-			if (aClass.isArray()) {
-				name = aClass.getComponentType().getName() + "[]";
-			} else {
-				name = aClass.getName();
-			}
-
-			Object type = javaSqlEnum.get(name);
-			if (type != null) {
-				return ((Number) type).intValue();
-			}
-
-			aClass = aClass.getSuperclass();
-		}
-
-		// check non-standard JDBC types that are still supported by JPA
-		if (javaClass.isArray()) {
-
-			Class<?> elementType = javaClass.getComponentType();
-			if (Character.class.isAssignableFrom(elementType) || Character.TYPE.isAssignableFrom(elementType)) {
-				return Types.VARCHAR;
-			} else if (Byte.class.isAssignableFrom(elementType) || Byte.TYPE.isAssignableFrom(elementType)) {
-				return Types.VARBINARY;
-			}
-		}
-
-		if (Calendar.class.isAssignableFrom(javaClass)) {
-			return Types.TIMESTAMP;
-		}
-
-		if (BigInteger.class.isAssignableFrom(javaClass)) {
-			return Types.BIGINT;
-		}
-
-		if (Serializable.class.isAssignableFrom(javaClass)) {
-			// serializable check should be the last one when all other mapping
-			// attempts failed
-			return Types.VARBINARY;
-		}
-
-		return NOT_DEFINED;
-	}
-
-	/**
-	 * Get the corresponding Java type by its java.sql.Types counterpart. Note
-	 * that this method should be used as a last resort, with explicit mapping
-	 * provided by user used as a first choice, as it can only guess how to map
-	 * certain types, such as NUMERIC, etc.
-	 *
-	 * @return Fully qualified Java type name or null if not found.
-	 */
-	@Override
-	public String getJavaByJdbcType(DbAttribute attribute, int type) {
-		String jdbcType = TypesMapping.getSqlNameByType(type);
-		DbType dbType;
-		if (attribute != null) {
-			dbType = new DbType(jdbcType, attribute.getMaxLength(), attribute.getAttributePrecision(),
-					attribute.getScale(), attribute.isMandatory());
-		} else {
-			dbType = new DbType(jdbcType);
-		}
-
-		String typeName = getJavaByJdbcType(dbType);
-
-		if (usePrimitives != null && usePrimitives) {
-			String primitive = classToPrimitive.get(typeName);
-			if (primitive != null) {
-				return primitive;
-			}
-		}
-
-		return typeName;
-	}
-
-	public String getJavaByJdbcType(DbType type) {
-		for (DbType t : dbTypes) {
-			if (t.isCover(type)) {
-				// because dbTypes sorted by specificity we will take first and
-				// the most specific matching
-				// that applicable for attribute
-				return mapping.get(t);
-			}
-		}
-
-		return null;
-	}
-
-	public Boolean getUsePrimitives() {
-		return usePrimitives;
-	}
-
-	public void setUsePrimitives(Boolean usePrimitives) {
-		this.usePrimitives = usePrimitives;
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/Jdbc2JavaTypeMapper.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/Jdbc2JavaTypeMapper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/Jdbc2JavaTypeMapper.java
deleted file mode 100644
index 160dc4e..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/loader/mapper/Jdbc2JavaTypeMapper.java
+++ /dev/null
@@ -1,33 +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.loader.mapper;
-
-import org.apache.cayenne.map.DbAttribute;
-
-/**
- * @since 4.0.
- */
-public interface Jdbc2JavaTypeMapper {
-
-    String getJavaByJdbcType(DbAttribute attribute, int type);
-
-    int getJdbcTypeByJava(DbAttribute attribute, String className);
-
-    void add(DbType type, String java);
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
index a8876f6..cb436e6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/AutoAdapter.java
@@ -33,7 +33,6 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SelectQuery;
@@ -216,11 +215,6 @@ public class AutoAdapter implements DbAdapter {
 	}
 
 	@Override
-	public MergerFactory mergerFactory() {
-		return getAdapter().mergerFactory();
-	}
-
-	@Override
 	public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
 		getAdapter().createTableAppendColumn(sqlBuffer, column);
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
index d72bc31..e89ae4b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/DbAdapter.java
@@ -29,7 +29,6 @@ import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SelectQuery;
@@ -183,11 +182,6 @@ public interface DbAdapter {
 	String tableTypeForView();
 
 	/**
-	 * @since 3.0
-	 */
-	MergerFactory mergerFactory();
-
-	/**
 	 * Append the column type part of a "create table" to the given
 	 * {@link StringBuffer}
 	 *

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
index 57d3430..979b425 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/JdbcAdapter.java
@@ -36,8 +36,11 @@ import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.log.JdbcEventLogger;
-import org.apache.cayenne.map.*;
-import org.apache.cayenne.merge.MergerFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SelectQuery;
@@ -600,13 +603,6 @@ public class JdbcAdapter implements DbAdapter {
 	}
 
 	/**
-	 * @since 3.0
-	 */
-	public MergerFactory mergerFactory() {
-		return new MergerFactory();
-	}
-
-	/**
 	 * @return
 	 * @since 4.0
 	 */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/PerAdapterProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/PerAdapterProvider.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/PerAdapterProvider.java
new file mode 100644
index 0000000..d4b2816
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/PerAdapterProvider.java
@@ -0,0 +1,46 @@
+/*****************************************************************
+ *   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.dba;
+
+import org.apache.cayenne.di.DIRuntimeException;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * An injectable provider that returns a given service in a context of a specific {@link DbAdapter}.
+ * This allows modules to create adapter-specific extensions without altering DbAdapter API.
+ *
+ * @since 4.0
+ */
+public class PerAdapterProvider<T> {
+
+    private Map<String, T> perAdapterValues;
+    private T defaultValue;
+
+    public PerAdapterProvider(Map<String, T> perAdapterValues, T defaultValue) {
+        this.perAdapterValues = Objects.requireNonNull(perAdapterValues);
+        this.defaultValue = Objects.requireNonNull(defaultValue);
+    }
+
+    public T get(DbAdapter adapter) throws DIRuntimeException {
+        T t = perAdapterValues.get(adapter.unwrap().getClass().getName());
+        return t != null ? t : defaultValue;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
index f71f655..edf8901 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2Adapter.java
@@ -24,7 +24,12 @@ import org.apache.cayenne.access.DataNode;
 import org.apache.cayenne.access.translator.ParameterBinding;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.types.*;
+import org.apache.cayenne.access.types.BooleanType;
+import org.apache.cayenne.access.types.ByteArrayType;
+import org.apache.cayenne.access.types.CharType;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -34,7 +39,6 @@ import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.resource.ResourceLocator;
@@ -223,11 +227,6 @@ public class DB2Adapter extends JdbcAdapter {
     }
 
     @Override
-    public MergerFactory mergerFactory() {
-        return new DB2MergerFactory();
-    }
-
-    @Override
     public void bindParameter(
             PreparedStatement statement, ParameterBinding binding) throws SQLException, Exception {
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2MergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2MergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2MergerFactory.java
deleted file mode 100644
index 8ae070d..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/db2/DB2MergerFactory.java
+++ /dev/null
@@ -1,49 +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.dba.db2;
-
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-
-
-public class DB2MergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createSetColumnTypeToDb(
-            final DbEntity entity,
-            DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" ALTER COLUMN ");
-                sqlBuffer.append(context.quotedName(columnNew));
-                sqlBuffer.append(" SET DATA TYPE ");
-            }
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
index 8a3b462..916eb9e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyAdapter.java
@@ -25,7 +25,12 @@ import org.apache.cayenne.access.translator.ejbql.EJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
-import org.apache.cayenne.access.types.*;
+import org.apache.cayenne.access.types.ByteType;
+import org.apache.cayenne.access.types.CharType;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
+import org.apache.cayenne.access.types.ShortType;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
 import org.apache.cayenne.dba.JdbcAdapter;
@@ -33,7 +38,6 @@ import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.resource.ResourceLocator;
 
 import java.sql.PreparedStatement;
@@ -44,14 +48,14 @@ import java.util.List;
 /**
  * DbAdapter implementation for the <a href="http://db.apache.org/derby/"> Derby RDBMS
  * </a>. Sample connection settings to use with Derby are shown below. <h3>Embedded</h3>
- * 
+ * <p>
  * <pre>
  *  test-derby.jdbc.url = jdbc:derby:testdb;create=true
  *  test-derby.jdbc.driver = org.apache.derby.jdbc.EmbeddedDriver
  * </pre>
- * 
+ * <p>
  * <h3>Network Server</h3>
- * 
+ * <p>
  * <pre>
  *  derbynet.jdbc.url = jdbc:derby://localhost/cayenne
  *  derbynet.jdbc.driver = org.apache.derby.jdbc.ClientDriver
@@ -103,7 +107,7 @@ public class DerbyAdapter extends JdbcAdapter {
     /**
      * Appends SQL for column creation to CREATE TABLE buffer. Only change for Derby is
      * that " NULL" is not supported.
-     * 
+     *
      * @since 1.2
      */
     @Override
@@ -118,7 +122,6 @@ public class DerbyAdapter extends JdbcAdapter {
         }
 
 
-
         sqlBuffer.append(quotingStrategy.quotedName(column));
         sqlBuffer.append(' ');
 
@@ -168,7 +171,7 @@ public class DerbyAdapter extends JdbcAdapter {
         translator.setCaseInsensitive(caseInsensitiveCollations);
         return translator;
     }
-    
+
     /**
      * @since 3.1
      */
@@ -180,11 +183,6 @@ public class DerbyAdapter extends JdbcAdapter {
     }
 
     @Override
-    public MergerFactory mergerFactory() {
-        return new DerbyMergerFactory();
-    }
-
-    @Override
     public void bindParameter(
             PreparedStatement statement,
             ParameterBinding binding) throws SQLException, Exception {
@@ -199,10 +197,14 @@ public class DerbyAdapter extends JdbcAdapter {
 
     private int convertNTypes(int sqlType) {
         switch (sqlType) {
-            case Types.NCHAR: return Types.CHAR;
-            case Types.NVARCHAR: return Types.VARCHAR;
-            case Types.LONGNVARCHAR: return Types.LONGVARCHAR;
-            case Types.NCLOB: return Types.CLOB;
+            case Types.NCHAR:
+                return Types.CHAR;
+            case Types.NVARCHAR:
+                return Types.VARCHAR;
+            case Types.LONGNVARCHAR:
+                return Types.LONGVARCHAR;
+            case Types.NCLOB:
+                return Types.CLOB;
 
             default:
                 return sqlType;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyMergerFactory.java
deleted file mode 100644
index 7ce30d4..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/derby/DerbyMergerFactory.java
+++ /dev/null
@@ -1,86 +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.dba.derby;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-import org.apache.cayenne.merge.SetNotNullToDb;
-
-public class DerbyMergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createSetColumnTypeToDb(
-            final DbEntity entity,
-            DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                // http://db.apache.org/derby/manuals/reference/sqlj26.html
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" ALTER ");
-                sqlBuffer.append(context.quotedName(columnNew));
-                sqlBuffer.append(" SET DATA TYPE ");
-            }
-        };
-    }
-
-    @Override
-    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetNotNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                QuotingStrategy context = adapter.getQuotingStrategy();
-
-                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
-                        + " ALTER COLUMN " + context.quotedName(getColumn()) + " NOT NULL");
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                QuotingStrategy context = adapter.getQuotingStrategy();
-
-                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
-                        + " ALTER COLUMN " + context.quotedName(getColumn()) + " NULL");
-            }
-
-        };
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
index 9146bd5..c88c88f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdAdapter.java
@@ -76,11 +76,7 @@ public class FirebirdAdapter extends JdbcAdapter {
         map.registerType(new CharType(true, false));
         
     }
-    
-    public FirebirdMergerFactory mergerFactory() {
-        return new FirebirdMergerFactory();
-    }
-    
+
     public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
 
         String[] types = externalTypesForJdbcType(column.getType());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdMergerFactory.java
deleted file mode 100644
index 45f3832..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/firebird/FirebirdMergerFactory.java
+++ /dev/null
@@ -1,91 +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.dba.firebird;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.AddColumnToDb;
-import org.apache.cayenne.merge.DropColumnToDb;
-import org.apache.cayenne.merge.SetNotNullToDb;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-
-public class FirebirdMergerFactory extends MergerFactory {
-    
-    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
-        return new DropColumnToDb(entity, column) {
-            public List<String> createSql(DbAdapter adapter) {
-                QuotingStrategy quoting = adapter.getQuotingStrategy();
-                return Collections.singletonList("ALTER TABLE " + quoting.quotedFullyQualifiedName(getEntity())
-                        + " DROP " + quoting.quotedName(getColumn()));
-            }
-        };
-    }
-    
-    @Override
-    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetNotNullToDb(entity, column) {
-            public List<String> createSql(DbAdapter adapter) {
-                QuotingStrategy context = adapter.getQuotingStrategy();
-                String entityName = context.quotedFullyQualifiedName(getEntity()) ;
-                String columnName = context.quotedName(getColumn());
-                // Firebird doesn't support ALTER TABLE table_name ALTER column_name SET NOT NULL
-                // but this might be achived by modyfication of system tables 
-                return Collections.singletonList(String.format("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = 1 "+ 
-                "WHERE RDB$FIELD_NAME = '%s' AND RDB$RELATION_NAME = '%s'", columnName, entityName));
-            }
-        };
-    }
-    
-    @Override
-    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-            public List<String> createSql(DbAdapter adapter) {
-                QuotingStrategy context = adapter.getQuotingStrategy();
-                String entityName = context.quotedFullyQualifiedName(getEntity()) ;
-                String columnName = context.quotedName(getColumn()); 
-                // Firebird doesn't support ALTER TABLE table_name ALTER column_name DROP NOT NULL
-                // but this might be achived by modyfication system tables 
-                return Collections.singletonList(String.format("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = NULL "+
-                " WHERE RDB$FIELD_NAME = '%s' AND RDB$RELATION_NAME = '%s'", columnName, entityName));
-            }
-        };
-    }
-    
-    
-    public MergerToken createAddColumnToDb(DbEntity entity, DbAttribute column) {
-        return new AddColumnToDb(entity, column) {
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" ADD ");
-                sqlBuffer.append(context.quotedName(getColumn()));
-                sqlBuffer.append(" ");
-            }
-        };
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
index 768d466..0d99869 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2Adapter.java
@@ -27,7 +27,6 @@ import org.apache.cayenne.dba.JdbcAdapter;
 import org.apache.cayenne.dba.PkGenerator;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.resource.ResourceLocator;
 
 import java.util.List;
@@ -57,11 +56,6 @@ public class H2Adapter extends JdbcAdapter {
     }
 
     @Override
-    public MergerFactory mergerFactory() {
-        return new H2MergerFactory();
-    }
-
-    @Override
     public void createTableAppendColumn(StringBuffer sqlBuffer, DbAttribute column) {
         super.createTableAppendColumn(sqlBuffer, column);
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2MergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2MergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2MergerFactory.java
deleted file mode 100644
index 6ec19aa..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/h2/H2MergerFactory.java
+++ /dev/null
@@ -1,83 +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.dba.h2;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-import org.apache.cayenne.merge.SetPrimaryKeyToDb;
-
-/**
- * @since 3.0
- */
-public class H2MergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" ALTER ");
-                sqlBuffer.append(context.quotedName(columnNew));
-                sqlBuffer.append(" ");
-            }
-        };
-    }
-
-    @Override
-    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                return Collections.singletonList("ALTER TABLE " + getEntity().getFullyQualifiedName()
-                        + " ALTER COLUMN " + getColumn().getName() + " SET NULL");
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetPrimaryKeyToDb(DbEntity entity, Collection<DbAttribute> primaryKeyOriginal,
-            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
-        return new SetPrimaryKeyToDb(entity, primaryKeyOriginal, primaryKeyNew, detectedPrimaryKeyName) {
-
-            @Override
-            protected void appendDropOriginalPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
-                sqls.add("ALTER TABLE " + adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity())
-                        + " DROP PRIMARY KEY");
-            }
-
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
index 6cfcaa5..96c5c0b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLDBAdapter.java
@@ -33,7 +33,6 @@ import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SelectQuery;
@@ -225,10 +224,4 @@ public class HSQLDBAdapter extends JdbcAdapter {
 			super.createTableAppendColumn(sqlBuffer, column);
 		}
 	}
-
-	@Override
-	public MergerFactory mergerFactory() {
-		return new HSQLMergerFactory();
-	}
-
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLMergerFactory.java
deleted file mode 100644
index e972b15..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/hsqldb/HSQLMergerFactory.java
+++ /dev/null
@@ -1,83 +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.dba.hsqldb;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-import org.apache.cayenne.merge.SetPrimaryKeyToDb;
-
-public class HSQLMergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" ALTER ");
-                sqlBuffer.append(context.quotedName(columnNew));
-                sqlBuffer.append(" ");
-            }
-        };
-    }
-
-    @Override
-    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                QuotingStrategy context = adapter.getQuotingStrategy();
-
-                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
-                        + " ALTER COLUMN " + context.quotedName(getColumn()) + " NULL");
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetPrimaryKeyToDb(DbEntity entity, Collection<DbAttribute> primaryKeyOriginal,
-            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
-        return new SetPrimaryKeyToDb(entity, primaryKeyOriginal, primaryKeyNew, detectedPrimaryKeyName) {
-
-            @Override
-            protected void appendDropOriginalPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
-                sqls.add("ALTER TABLE " + adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity())
-                        + " DROP PRIMARY KEY");
-            }
-
-        };
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
index d9aee83..dc94bae 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresAdapter.java
@@ -37,7 +37,6 @@ import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SelectQuery;
@@ -121,11 +120,6 @@ public class IngresAdapter extends JdbcAdapter {
 	}
 
 	@Override
-	public MergerFactory mergerFactory() {
-		return new IngresMergerFactory();
-	}
-
-	@Override
 	public void createTableAppendColumn(StringBuffer buf, DbAttribute at) {
 
 		String[] types = externalTypesForJdbcType(at.getType());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresMergerFactory.java
deleted file mode 100644
index f8da652..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/ingres/IngresMergerFactory.java
+++ /dev/null
@@ -1,226 +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.dba.ingres;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.merge.AddRelationshipToDb;
-import org.apache.cayenne.merge.DropColumnToDb;
-import org.apache.cayenne.merge.DropRelationshipToDb;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-import org.apache.cayenne.merge.SetNotNullToDb;
-
-public class IngresMergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" ALTER COLUMN ");
-                sqlBuffer.append(context.quotedName(columnNew));
-                sqlBuffer.append(" ");
-            }
-        };
-    }
-
-    @Override
-    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
-        return new DropColumnToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuilder buf = new StringBuilder();
-                QuotingStrategy context = adapter.getQuotingStrategy();
-                buf.append("ALTER TABLE ");
-                buf.append(context.quotedFullyQualifiedName(getEntity()));
-                buf.append(" DROP COLUMN ");
-                buf.append(context.quotedName(getColumn()));
-                buf.append(" RESTRICT ");
-
-                return Collections.singletonList(buf.toString());
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createAddRelationshipToDb(DbEntity entity, final DbRelationship rel) {
-        return new AddRelationshipToDb(entity, rel) {
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                if (!rel.isToMany() && rel.isToPK() && !rel.isToDependentPK()) {
-
-                    DbEntity source = (DbEntity) rel.getSourceEntity();
-                    QuotingStrategy context = adapter.getQuotingStrategy();
-                    StringBuilder buf = new StringBuilder();
-                    StringBuilder refBuf = new StringBuilder();
-
-                    buf.append("ALTER TABLE ");
-                    buf.append(context.quotedFullyQualifiedName(source));
-
-                    // requires the ADD CONSTRAINT statement
-                    buf.append(" ADD CONSTRAINT ");
-                    String name = "U_" + rel.getSourceEntity().getName() + "_"
-                            + (long) (System.currentTimeMillis() / (Math.random() * 100000));
-
-                    buf.append(context.quotedIdentifier(rel.getSourceEntity(), name));
-                    buf.append(" FOREIGN KEY (");
-
-                    boolean first = true;
-                    for (DbJoin join : rel.getJoins()) {
-                        if (!first) {
-                            buf.append(", ");
-                            refBuf.append(", ");
-                        } else
-                            first = false;
-
-                        buf.append(context.quotedSourceName(join));
-                        refBuf.append(context.quotedTargetName(join));
-                    }
-
-                    buf.append(") REFERENCES ");
-                    buf.append(context.quotedFullyQualifiedName((DbEntity) rel.getTargetEntity()));
-                    buf.append(" (");
-                    buf.append(refBuf.toString());
-                    buf.append(')');
-
-                    // also make sure we delete dependent FKs
-                    buf.append(" ON DELETE CASCADE");
-
-                    String fksql = buf.toString();
-
-                    if (fksql != null) {
-                        return Collections.singletonList(fksql);
-                    }
-                }
-
-                return Collections.emptyList();
-
-            }
-        };
-    }
-
-    @Override
-    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetNotNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-
-                /*
-                 * TODO: we generate this query as in ingres db documentation,
-                 * but unfortunately ingres don't support it
-                 */
-
-                StringBuilder sqlBuffer = new StringBuilder();
-
-                QuotingStrategy context = adapter.getQuotingStrategy();
-
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(getEntity().getFullyQualifiedName());
-                sqlBuffer.append(" ALTER COLUMN ");
-                sqlBuffer.append(context.quotedName(getColumn()));
-                sqlBuffer.append(" ");
-                sqlBuffer.append(adapter.externalTypesForJdbcType(getColumn().getType())[0]);
-
-                if (adapter.typeSupportsLength(getColumn().getType()) && getColumn().getMaxLength() > 0) {
-                    sqlBuffer.append("(");
-                    sqlBuffer.append(getColumn().getMaxLength());
-                    sqlBuffer.append(")");
-                }
-
-                sqlBuffer.append(" NOT NULL");
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuilder sqlBuffer = new StringBuilder();
-                QuotingStrategy context = adapter.getQuotingStrategy();
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" ALTER COLUMN ");
-                sqlBuffer.append(context.quotedName(getColumn()));
-                sqlBuffer.append(" ");
-                sqlBuffer.append(adapter.externalTypesForJdbcType(getColumn().getType())[0]);
-
-                if (adapter.typeSupportsLength(getColumn().getType()) && getColumn().getMaxLength() > 0) {
-                    sqlBuffer.append("(");
-                    sqlBuffer.append(getColumn().getMaxLength());
-                    sqlBuffer.append(")");
-                }
-
-                sqlBuffer.append(" WITH NULL");
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createDropRelationshipToDb(final DbEntity entity, DbRelationship rel) {
-
-        return new DropRelationshipToDb(entity, rel) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                String fkName = getFkName();
-
-                if (fkName == null) {
-                    return Collections.emptyList();
-                }
-                
-                StringBuilder buf = new StringBuilder();
-                buf.append("ALTER TABLE ");
-                buf.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
-                buf.append(" DROP CONSTRAINT ");
-                buf.append(fkName);
-                buf.append(" CASCADE ");
-
-                return Collections.singletonList(buf.toString());
-            }
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
index 961f653..1ecac8a 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLAdapter.java
@@ -27,16 +27,23 @@ import org.apache.cayenne.access.translator.ejbql.JdbcEJBQLTranslatorFactory;
 import org.apache.cayenne.access.translator.select.QualifierTranslator;
 import org.apache.cayenne.access.translator.select.QueryAssembler;
 import org.apache.cayenne.access.translator.select.SelectTranslator;
-import org.apache.cayenne.access.types.*;
+import org.apache.cayenne.access.types.ByteArrayType;
+import org.apache.cayenne.access.types.CharType;
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.access.types.ExtendedTypeMap;
 import org.apache.cayenne.configuration.Constants;
 import org.apache.cayenne.configuration.RuntimeProperties;
-import org.apache.cayenne.dba.*;
+import org.apache.cayenne.dba.DefaultQuotingStrategy;
+import org.apache.cayenne.dba.JdbcAdapter;
+import org.apache.cayenne.dba.PkGenerator;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.Query;
 import org.apache.cayenne.query.SQLAction;
 import org.apache.cayenne.query.SelectQuery;
@@ -45,7 +52,13 @@ import org.apache.cayenne.resource.ResourceLocator;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.sql.Types;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
 
 /**
  * DbAdapter implementation for <a href="http://www.mysql.com">MySQL RDBMS</a>.
@@ -378,11 +391,6 @@ public class MySQLAdapter extends JdbcAdapter {
 		}
 	}
 
-	@Override
-	public MergerFactory mergerFactory() {
-		return new MySQLMergerFactory();
-	}
-
 	final class PKComparator implements Comparator<DbAttribute> {
 
 		public int compare(DbAttribute a1, DbAttribute a2) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLMergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLMergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLMergerFactory.java
deleted file mode 100644
index 43f4b55..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/mysql/MySQLMergerFactory.java
+++ /dev/null
@@ -1,157 +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.dba.mysql;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.merge.DropRelationshipToDb;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.SetAllowNullToDb;
-import org.apache.cayenne.merge.SetColumnTypeToDb;
-import org.apache.cayenne.merge.SetNotNullToDb;
-import org.apache.cayenne.merge.SetPrimaryKeyToDb;
-
-public class MySQLMergerFactory extends MergerFactory {
-
-    @Override
-    public MergerToken createSetNotNullToDb(
-            final DbEntity entity,
-            final DbAttribute column) {
-        return new SetNotNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuffer sqlBuffer = new StringBuffer();
-
-                QuotingStrategy context = adapter.getQuotingStrategy();
-
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" CHANGE ");
-                sqlBuffer.append(context.quotedName(getColumn()));
-                sqlBuffer.append(" ");
-                adapter.createTableAppendColumn(sqlBuffer, column);
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetAllowNullToDb(
-            final DbEntity entity,
-            final DbAttribute column) {
-        return new SetAllowNullToDb(entity, column) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                StringBuffer sqlBuffer = new StringBuffer();
-
-                QuotingStrategy context = adapter.getQuotingStrategy();
-
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-                sqlBuffer.append(" CHANGE ");
-                sqlBuffer.append(context.quotedName(getColumn()));
-                sqlBuffer.append(" ");
-                adapter.createTableAppendColumn(sqlBuffer, column);
-
-                return Collections.singletonList(sqlBuffer.toString());
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createSetColumnTypeToDb(
-            final DbEntity entity,
-            DbAttribute columnOriginal,
-            final DbAttribute columnNew) {
-
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
-
-            @Override
-            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-                // http://dev.mysql.com/tech-resources/articles/mysql-cluster-50.html
-                sqlBuffer.append("ALTER TABLE ");
-                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
-                sqlBuffer.append(" MODIFY ");
-                sqlBuffer.append(context.quotedName(columnNew));
-                sqlBuffer.append(" ");
-            }
-
-        };
-    }
-
-    @Override
-    public MergerToken createDropRelationshipToDb(
-            final DbEntity entity,
-            DbRelationship rel) {
-
-        return new DropRelationshipToDb(entity, rel) {
-
-            @Override
-            public List<String> createSql(DbAdapter adapter) {
-                String fkName = getFkName();
-
-                if (fkName == null) {
-                    return Collections.emptyList();
-                }
-                QuotingStrategy context = adapter.getQuotingStrategy();
-
-                // http://dev.mysql.com/tech-resources/articles/mysql-cluster-50.html
-                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(entity) + " DROP FOREIGN KEY " + fkName);
-            }
-        };
-    }
-    
-    @Override
-    public MergerToken createSetPrimaryKeyToDb(
-            DbEntity entity,
-            Collection<DbAttribute> primaryKeyOriginal,
-            Collection<DbAttribute> primaryKeyNew,
-            String detectedPrimaryKeyName) {
-        return new SetPrimaryKeyToDb(
-                entity,
-                primaryKeyOriginal,
-                primaryKeyNew,
-                detectedPrimaryKeyName) {
-
-            @Override
-            protected void appendDropOriginalPrimaryKeySQL(
-                    DbAdapter adapter,
-                    List<String> sqls) {
-                sqls.add("ALTER TABLE "
-                        + adapter.getQuotingStrategy()
-                                .quotedFullyQualifiedName(getEntity())
-                        + " DROP PRIMARY KEY");
-            }
-
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
index c4a50db..a426332 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/dba/openbase/OpenBaseAdapter.java
@@ -39,7 +39,6 @@ import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.DbJoin;
 import org.apache.cayenne.map.DbRelationship;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.merge.MergerFactory;
 import org.apache.cayenne.query.SelectQuery;
 import org.apache.cayenne.resource.ResourceLocator;
 
@@ -53,251 +52,245 @@ import java.util.List;
 /**
  * DbAdapter implementation for <a href="http://www.openbase.com">OpenBase</a>.
  * Sample connection settings to use with OpenBase are shown below:
- * 
+ * <p>
  * <pre>
  * openbase.jdbc.username = test
  * openbase.jdbc.password = secret
  * openbase.jdbc.url = jdbc:openbase://serverhostname/cayenne
  * openbase.jdbc.driver = com.openbase.jdbc.ObDriver
  * </pre>
- * 
+ *
  * @since 1.1
  */
 public class OpenBaseAdapter extends JdbcAdapter {
 
-	public OpenBaseAdapter(@Inject RuntimeProperties runtimeProperties,
-			@Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
-			@Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
-			@Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
-			@Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
-		super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
-
-		// init defaults
-		this.setSupportsUniqueConstraints(false);
-	}
-
-	/**
-	 * @since 4.0
-	 */
-	@Override
-	public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
-		return new OpenBaseSelectTranslator(query, this, entityResolver);
-	}
-
-	@Override
-	protected void configureExtendedTypes(ExtendedTypeMap map) {
-		super.configureExtendedTypes(map);
-
-		// Byte handling doesn't work on read...
-		// need special converter
-		map.registerType(new OpenBaseByteType());
-
-		map.registerType(new OpenBaseCharType());
-	}
-
-	@Override
-	public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) {
-
-		// OpenBase makes no distinction between CHAR and VARCHAR
-		// so lets use VARCHAR, since it seems more generic
-		if (type == Types.CHAR) {
-			type = Types.VARCHAR;
-		}
-
-		return super.buildAttribute(name, typeName, type, size, scale, allowNulls);
-	}
-
-	/**
-	 * Returns word "go".
-	 */
-	@Override
-	public String getBatchTerminator() {
-		return "go";
-	}
-
-	/**
-	 * Returns null, since views are not yet supported in openbase.
-	 */
-	@Override
-	public String tableTypeForView() {
-		// TODO: according to OpenBase docs views *ARE* supported.
-		return null;
-	}
-
-	/**
-	 * Returns OpenBase-specific translator for queries.
-	 */
-	@Override
-	public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
-		return new OpenBaseQualifierTranslator(queryAssembler);
-	}
-
-	/**
-	 * Creates and returns a primary key generator. Overrides superclass
-	 * implementation to return an instance of OpenBasePkGenerator that uses
-	 * built-in multi-server primary key generation.
-	 */
-	@Override
-	protected PkGenerator createPkGenerator() {
-		return new OpenBasePkGenerator(this);
-	}
-
-	/**
-	 * Returns a SQL string that can be used to create database table
-	 * corresponding to <code>ent</code> parameter.
-	 */
-	@Override
-	public String createTable(DbEntity ent) {
-
-		StringBuilder buf = new StringBuilder();
-
-		buf.append("CREATE TABLE ");
-		buf.append(quotingStrategy.quotedFullyQualifiedName(ent));
-		buf.append(" (");
-
-		// columns
-		Iterator<DbAttribute> it = ent.getAttributes().iterator();
-		boolean first = true;
-		while (it.hasNext()) {
-			if (first) {
-				first = false;
-			} else {
-				buf.append(", ");
-			}
-
-			DbAttribute at = it.next();
-
-			// attribute may not be fully valid, do a simple check
-			if (at.getType() == TypesMapping.NOT_DEFINED) {
-				throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
-						+ at.getName() + "'.");
-			}
-
-			String[] types = externalTypesForJdbcType(at.getType());
-			if (types == null || types.length == 0) {
-				throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
-						+ at.getName() + "': " + at.getType());
-			}
-
-			String type = types[0];
-			buf.append(quotingStrategy.quotedName(at)).append(' ').append(type);
-
-			// append size and precision (if applicable)
-			if (typeSupportsLength(at.getType())) {
-				int len = at.getMaxLength();
-				int scale = TypesMapping.isDecimal(at.getType()) ? at.getScale() : -1;
-
-				// sanity check
-				if (scale > len) {
-					scale = -1;
-				}
-
-				if (len > 0) {
-					buf.append('(').append(len);
-
-					if (scale >= 0) {
-						buf.append(", ").append(scale);
-					}
-
-					buf.append(')');
-				}
-			}
-
-			if (at.isMandatory()) {
-				buf.append(" NOT NULL");
-			} else {
-				buf.append(" NULL");
-			}
-		}
-
-		buf.append(')');
-		return buf.toString();
-	}
-
-	/**
-	 * Returns a SQL string that can be used to create a foreign key constraint
-	 * for the relationship.
-	 */
-	@Override
-	public String createFkConstraint(DbRelationship rel) {
-		StringBuilder buf = new StringBuilder();
-
-		// OpendBase Specifics is that we need to create a constraint going
-		// from destination to source for this to work...
-
-		DbEntity sourceEntity = (DbEntity) rel.getSourceEntity();
-		DbEntity targetEntity = (DbEntity) rel.getTargetEntity();
-		String toMany = (!rel.isToMany()) ? "'1'" : "'0'";
-
-		// TODO: doesn't seem like OpenBase supports compound joins...
-		// need to doublecheck that
-
-		int joinsLen = rel.getJoins().size();
-		if (joinsLen == 0) {
-			throw new CayenneRuntimeException("Relationship has no joins: " + rel.getName());
-		} else if (joinsLen > 1) {
-			// ignore extra joins
-		}
-
-		DbJoin join = rel.getJoins().get(0);
-
-		buf.append("INSERT INTO _SYS_RELATIONSHIP (").append("dest_table, dest_column, source_table, source_column, ")
-				.append("block_delete, cascade_delete, one_to_many, operator, relationshipName").append(") VALUES ('")
-				.append(sourceEntity.getFullyQualifiedName()).append("', '").append(join.getSourceName())
-				.append("', '").append(targetEntity.getFullyQualifiedName()).append("', '")
-				.append(join.getTargetName()).append("', 0, 0, ").append(toMany).append(", '=', '")
-				.append(rel.getName()).append("')");
-
-		return buf.toString();
-	}
-
-	// OpenBase JDBC driver has trouble reading "integer" as byte
-	// this converter addresses such problem
-	static class OpenBaseByteType extends ByteType {
-
-		OpenBaseByteType() {
-			super(true);
-		}
-
-		@Override
-		public Object materializeObject(ResultSet rs, int index, int type) throws Exception {
-
-			// read value as int, and then narrow it down
-			int val = rs.getInt(index);
-			return (rs.wasNull()) ? null : Byte.valueOf((byte) val);
-		}
-
-		@Override
-		public Object materializeObject(CallableStatement rs, int index, int type) throws Exception {
-
-			// read value as int, and then narrow it down
-			int val = rs.getInt(index);
-			return (rs.wasNull()) ? null : Byte.valueOf((byte) val);
-		}
-	}
-
-	static class OpenBaseCharType extends CharType {
-
-		OpenBaseCharType() {
-			super(false, true);
-		}
-
-		@Override
-		public void setJdbcObject(PreparedStatement st, Object val, int pos, int type, int precision) throws Exception {
-
-			// These to types map to "text"; and when setting "text" as object
-			// OB assumes that the object is the actual CLOB... weird
-			if (type == Types.CLOB || type == Types.LONGVARCHAR) {
-				st.setString(pos, (String) val);
-			} else {
-				super.setJdbcObject(st, val, pos, type, precision);
-			}
-		}
-	}
-
-	@Override
-	public MergerFactory mergerFactory() {
-		return new OpenBaseMergerFactory();
-	}
-
+    public OpenBaseAdapter(@Inject RuntimeProperties runtimeProperties,
+                           @Inject(Constants.SERVER_DEFAULT_TYPES_LIST) List<ExtendedType> defaultExtendedTypes,
+                           @Inject(Constants.SERVER_USER_TYPES_LIST) List<ExtendedType> userExtendedTypes,
+                           @Inject(Constants.SERVER_TYPE_FACTORIES_LIST) List<ExtendedTypeFactory> extendedTypeFactories,
+                           @Inject(Constants.SERVER_RESOURCE_LOCATOR) ResourceLocator resourceLocator) {
+        super(runtimeProperties, defaultExtendedTypes, userExtendedTypes, extendedTypeFactories, resourceLocator);
+
+        // init defaults
+        this.setSupportsUniqueConstraints(false);
+    }
+
+    /**
+     * @since 4.0
+     */
+    @Override
+    public SelectTranslator getSelectTranslator(SelectQuery<?> query, EntityResolver entityResolver) {
+        return new OpenBaseSelectTranslator(query, this, entityResolver);
+    }
+
+    @Override
+    protected void configureExtendedTypes(ExtendedTypeMap map) {
+        super.configureExtendedTypes(map);
+
+        // Byte handling doesn't work on read...
+        // need special converter
+        map.registerType(new OpenBaseByteType());
+
+        map.registerType(new OpenBaseCharType());
+    }
+
+    @Override
+    public DbAttribute buildAttribute(String name, String typeName, int type, int size, int scale, boolean allowNulls) {
+
+        // OpenBase makes no distinction between CHAR and VARCHAR
+        // so lets use VARCHAR, since it seems more generic
+        if (type == Types.CHAR) {
+            type = Types.VARCHAR;
+        }
+
+        return super.buildAttribute(name, typeName, type, size, scale, allowNulls);
+    }
+
+    /**
+     * Returns word "go".
+     */
+    @Override
+    public String getBatchTerminator() {
+        return "go";
+    }
+
+    /**
+     * Returns null, since views are not yet supported in openbase.
+     */
+    @Override
+    public String tableTypeForView() {
+        // TODO: according to OpenBase docs views *ARE* supported.
+        return null;
+    }
+
+    /**
+     * Returns OpenBase-specific translator for queries.
+     */
+    @Override
+    public QualifierTranslator getQualifierTranslator(QueryAssembler queryAssembler) {
+        return new OpenBaseQualifierTranslator(queryAssembler);
+    }
+
+    /**
+     * Creates and returns a primary key generator. Overrides superclass
+     * implementation to return an instance of OpenBasePkGenerator that uses
+     * built-in multi-server primary key generation.
+     */
+    @Override
+    protected PkGenerator createPkGenerator() {
+        return new OpenBasePkGenerator(this);
+    }
+
+    /**
+     * Returns a SQL string that can be used to create database table
+     * corresponding to <code>ent</code> parameter.
+     */
+    @Override
+    public String createTable(DbEntity ent) {
+
+        StringBuilder buf = new StringBuilder();
+
+        buf.append("CREATE TABLE ");
+        buf.append(quotingStrategy.quotedFullyQualifiedName(ent));
+        buf.append(" (");
+
+        // columns
+        Iterator<DbAttribute> it = ent.getAttributes().iterator();
+        boolean first = true;
+        while (it.hasNext()) {
+            if (first) {
+                first = false;
+            } else {
+                buf.append(", ");
+            }
+
+            DbAttribute at = it.next();
+
+            // attribute may not be fully valid, do a simple check
+            if (at.getType() == TypesMapping.NOT_DEFINED) {
+                throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
+                        + at.getName() + "'.");
+            }
+
+            String[] types = externalTypesForJdbcType(at.getType());
+            if (types == null || types.length == 0) {
+                throw new CayenneRuntimeException("Undefined type for attribute '" + ent.getFullyQualifiedName() + "."
+                        + at.getName() + "': " + at.getType());
+            }
+
+            String type = types[0];
+            buf.append(quotingStrategy.quotedName(at)).append(' ').append(type);
+
+            // append size and precision (if applicable)
+            if (typeSupportsLength(at.getType())) {
+                int len = at.getMaxLength();
+                int scale = TypesMapping.isDecimal(at.getType()) ? at.getScale() : -1;
+
+                // sanity check
+                if (scale > len) {
+                    scale = -1;
+                }
+
+                if (len > 0) {
+                    buf.append('(').append(len);
+
+                    if (scale >= 0) {
+                        buf.append(", ").append(scale);
+                    }
+
+                    buf.append(')');
+                }
+            }
+
+            if (at.isMandatory()) {
+                buf.append(" NOT NULL");
+            } else {
+                buf.append(" NULL");
+            }
+        }
+
+        buf.append(')');
+        return buf.toString();
+    }
+
+    /**
+     * Returns a SQL string that can be used to create a foreign key constraint
+     * for the relationship.
+     */
+    @Override
+    public String createFkConstraint(DbRelationship rel) {
+        StringBuilder buf = new StringBuilder();
+
+        // OpendBase Specifics is that we need to create a constraint going
+        // from destination to source for this to work...
+
+        DbEntity sourceEntity = (DbEntity) rel.getSourceEntity();
+        DbEntity targetEntity = (DbEntity) rel.getTargetEntity();
+        String toMany = (!rel.isToMany()) ? "'1'" : "'0'";
+
+        // TODO: doesn't seem like OpenBase supports compound joins...
+        // need to doublecheck that
+
+        int joinsLen = rel.getJoins().size();
+        if (joinsLen == 0) {
+            throw new CayenneRuntimeException("Relationship has no joins: " + rel.getName());
+        } else if (joinsLen > 1) {
+            // ignore extra joins
+        }
+
+        DbJoin join = rel.getJoins().get(0);
+
+        buf.append("INSERT INTO _SYS_RELATIONSHIP (").append("dest_table, dest_column, source_table, source_column, ")
+                .append("block_delete, cascade_delete, one_to_many, operator, relationshipName").append(") VALUES ('")
+                .append(sourceEntity.getFullyQualifiedName()).append("', '").append(join.getSourceName())
+                .append("', '").append(targetEntity.getFullyQualifiedName()).append("', '")
+                .append(join.getTargetName()).append("', 0, 0, ").append(toMany).append(", '=', '")
+                .append(rel.getName()).append("')");
+
+        return buf.toString();
+    }
+
+    // OpenBase JDBC driver has trouble reading "integer" as byte
+    // this converter addresses such problem
+    static class OpenBaseByteType extends ByteType {
+
+        OpenBaseByteType() {
+            super(true);
+        }
+
+        @Override
+        public Object materializeObject(ResultSet rs, int index, int type) throws Exception {
+
+            // read value as int, and then narrow it down
+            int val = rs.getInt(index);
+            return (rs.wasNull()) ? null : Byte.valueOf((byte) val);
+        }
+
+        @Override
+        public Object materializeObject(CallableStatement rs, int index, int type) throws Exception {
+
+            // read value as int, and then narrow it down
+            int val = rs.getInt(index);
+            return (rs.wasNull()) ? null : Byte.valueOf((byte) val);
+        }
+    }
+
+    static class OpenBaseCharType extends CharType {
+
+        OpenBaseCharType() {
+            super(false, true);
+        }
+
+        @Override
+        public void setJdbcObject(PreparedStatement st, Object val, int pos, int type, int precision) throws Exception {
+
+            // These to types map to "text"; and when setting "text" as object
+            // OB assumes that the object is the actual CLOB... weird
+            if (type == Types.CLOB || type == Types.LONGVARCHAR) {
+                st.setString(pos, (String) val);
+            } else {
+                super.setJdbcObject(st, val, pos, type, precision);
+            }
+        }
+    }
 }


[11/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/IncludeTableFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/IncludeTableFilter.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/IncludeTableFilter.java
new file mode 100644
index 0000000..ad68b49
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/IncludeTableFilter.java
@@ -0,0 +1,71 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.reverse.filters;
+
+import java.util.regex.Pattern;
+
+/**
+* @since 4.0.
+*/
+public class IncludeTableFilter implements Comparable<IncludeTableFilter> {
+    public final Pattern pattern;
+
+    public final PatternFilter columnsFilter;
+
+    public IncludeTableFilter(String pattern) {
+        this(pattern, PatternFilter.INCLUDE_EVERYTHING);
+    }
+
+    public IncludeTableFilter(String pattern, PatternFilter columnsFilter) {
+        this.pattern = PatternFilter.pattern(pattern);
+        this.columnsFilter = columnsFilter;
+    }
+
+    public boolean isIncludeColumn (String name) {
+        return columnsFilter.isInclude(name);
+    }
+
+    @Override
+    public int compareTo(IncludeTableFilter o) {
+        if (pattern == null && o.pattern == null) {
+            return 0;
+        } else if (pattern == null) {
+            return 1;
+        } else if (o.pattern == null) {
+            return -1;
+        } else {
+            return pattern.pattern().compareTo(o.pattern.pattern());
+        }
+
+    }
+
+
+    @Override
+    public String toString() {
+        return toString(new StringBuilder(), "").toString();
+    }
+
+    protected StringBuilder toString(StringBuilder res, String prefix) {
+        res.append(prefix).append("Include: ").append(String.valueOf(pattern)).append(" Columns: ");
+        columnsFilter.toString(res);
+        res.append("\n");
+
+        return res;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/LegacyFilterConfigBridge.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/LegacyFilterConfigBridge.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/LegacyFilterConfigBridge.java
new file mode 100644
index 0000000..b7e66fc
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/LegacyFilterConfigBridge.java
@@ -0,0 +1,150 @@
+/*****************************************************************
+ *   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.dbsync.reverse.filters;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+/**
+ * @since 4.0
+ */
+public class LegacyFilterConfigBridge {
+
+    private String catalog;
+    private String schema;
+
+    private String includeTableFilters;
+    private String includeColumnFilters;
+    private String includeProceduresFilters;
+    private String excludeTableFilters;
+    private String excludeColumnFilters;
+    private String excludeProceduresFilters;
+
+    private boolean loadProcedures;
+
+    public LegacyFilterConfigBridge() {
+    }
+
+    public LegacyFilterConfigBridge catalog(String catalog) {
+        this.catalog = catalog;
+        return this;
+    }
+
+    public String catalog() {
+        return catalog;
+    }
+
+    public LegacyFilterConfigBridge schema(String schema) {
+        this.schema = schema;
+        return this;
+    }
+
+    public String schema() {
+        return schema;
+    }
+
+    public LegacyFilterConfigBridge includeTables(String tableFilters) {
+        if (isBlank(tableFilters)) {
+            return this;
+        }
+
+        this.includeTableFilters = transform(tableFilters);
+        return this;
+    }
+
+    public LegacyFilterConfigBridge includeColumns(String columnFilters) {
+        if (isBlank(columnFilters)) {
+            return this;
+        }
+
+        this.includeColumnFilters = transform(columnFilters);
+        return this;
+    }
+
+    public LegacyFilterConfigBridge includeProcedures(String proceduresFilters) {
+        if (isBlank(proceduresFilters)) {
+            return this;
+        }
+
+        this.includeProceduresFilters = transform(proceduresFilters);
+        return this;
+    }
+
+    public LegacyFilterConfigBridge excludeTables(String tableFilters) {
+        if (isBlank(tableFilters)) {
+            return this;
+        }
+
+        this.excludeTableFilters = transform(tableFilters);
+        return this;
+    }
+
+    public LegacyFilterConfigBridge excludeColumns(String columnFilters) {
+        if (isBlank(columnFilters)) {
+            return this;
+        }
+
+        this.excludeColumnFilters = transform(columnFilters);
+        return this;
+    }
+
+    public LegacyFilterConfigBridge excludeProcedures(String proceduresFilters) {
+        if (isBlank(proceduresFilters)) {
+            return this;
+        }
+
+        this.excludeProceduresFilters = transform(proceduresFilters);
+        return this;
+    }
+
+    private static String transform(String pattern) {
+        return "^" + pattern.replaceAll("[*?]", ".$0") + "$";
+    }
+
+    public void setProceduresFilters(boolean loadProcedures) {
+        this.loadProcedures = loadProcedures;
+    }
+
+    public String getIncludeTableFilters() {
+        return includeTableFilters;
+    }
+
+    public String getIncludeColumnFilters() {
+        return includeColumnFilters;
+    }
+
+    public String getIncludeProceduresFilters() {
+        return includeProceduresFilters;
+    }
+
+    public String getExcludeTableFilters() {
+        return excludeTableFilters;
+    }
+
+    public String getExcludeColumnFilters() {
+        return excludeColumnFilters;
+    }
+
+    public String getExcludeProceduresFilters() {
+        return excludeProceduresFilters;
+    }
+
+    public boolean isLoadProcedures() {
+        return loadProcedures;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilter.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilter.java
new file mode 100644
index 0000000..4b0bf45
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilter.java
@@ -0,0 +1,169 @@
+/*****************************************************************
+ *   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.dbsync.reverse.filters;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.util.Comparator;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+/**
+ * @since 4.0
+ */
+public class PatternFilter {
+
+    public static final PatternFilter INCLUDE_EVERYTHING = new PatternFilter() {
+
+        @Override
+        public boolean isInclude(String obj) {
+            return true;
+        }
+
+        @Override
+        public StringBuilder toString(StringBuilder res) {
+            return res.append("ALL");
+        }
+    };
+
+    public static final PatternFilter INCLUDE_NOTHING = new PatternFilter() {
+        @Override
+        public boolean isInclude(String obj) {
+            return false;
+        }
+
+        @Override
+        public StringBuilder toString(StringBuilder res) {
+            return res.append("NONE");
+        }
+    };
+
+    public static final Comparator<Pattern> PATTERN_COMPARATOR = new Comparator<Pattern>() {
+        @Override
+        public int compare(Pattern o1, Pattern o2) {
+            if (o1 != null && o2 != null) {
+                return o1.pattern().compareTo(o2.pattern());
+            }
+            else {
+                return -1;
+            }
+        }
+    };
+
+    private final SortedSet<Pattern> includes;
+    private final SortedSet<Pattern> excludes;
+
+    public PatternFilter() {
+        this.includes = new TreeSet<>(PATTERN_COMPARATOR);
+        this.excludes = new TreeSet<>(PATTERN_COMPARATOR);
+    }
+
+    public PatternFilter include(Pattern p) {
+        includes.add(p);
+
+        return this;
+    }
+
+    public PatternFilter exclude(Pattern p) {
+        excludes.add(p);
+
+        return this;
+    }
+
+    public static Pattern pattern(String pattern) {
+        if (pattern == null) {
+            return null;
+        }
+        return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
+    }
+
+    public PatternFilter include(String p) {
+        return include(pattern(p));
+    }
+
+    public PatternFilter exclude(String p) {
+        return exclude(pattern(p));
+    }
+
+    public boolean isInclude(String obj) {
+        boolean include = includes.isEmpty();
+        for (Pattern p : includes) {
+            if (p != null) {
+                if (p.matcher(obj).matches()) {
+                    include = true;
+                    break;
+                }
+            }
+        }
+
+        if (!include) {
+            return false;
+        }
+
+        for (Pattern p : excludes) {
+            if (p.matcher(obj).matches()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+
+        PatternFilter filter = (PatternFilter) o;
+        return includes.equals(filter.includes)
+                && excludes.equals(filter.excludes);
+    }
+
+    @Override
+    public int hashCode() {
+        return includes.hashCode();
+    }
+
+    public StringBuilder toString(StringBuilder res) {
+        if (includes.isEmpty()) {
+            // Do nothing.
+        } else if (includes.size() > 1) {
+            res.append("(").append(StringUtils.join(includes, " OR ")).append(")");
+        } else {
+            res.append(includes.first().pattern());
+        }
+
+        if (!excludes.isEmpty()) {
+            res.append(" AND NOT (").append(StringUtils.join(includes, " OR ")).append(")");
+        }
+
+        return res;
+    }
+
+    public boolean isEmpty() {
+        return includes.isEmpty() && excludes.isEmpty();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/SchemaFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/SchemaFilter.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/SchemaFilter.java
new file mode 100644
index 0000000..d486215
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/SchemaFilter.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.dbsync.reverse.filters;
+
+/**
+* @since 4.0.
+*/
+public class SchemaFilter {
+    public final String name;
+    public final TableFilter tables;
+    public final PatternFilter procedures;
+
+    public SchemaFilter(String name, TableFilter tables, PatternFilter procedures) {
+        this.name = name;
+        this.tables = tables;
+        this.procedures = procedures;
+    }
+
+    protected StringBuilder toString(StringBuilder res, String prefix) {
+        res.append(prefix).append("Schema: ").append(name).append("\n");
+        tables.toString(res, prefix + "  ");
+
+        res.append(prefix).append("  Procedures: ");
+        procedures.toString(res).append("\n");
+
+        return res;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/TableFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/TableFilter.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/TableFilter.java
new file mode 100644
index 0000000..266bbc2
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/TableFilter.java
@@ -0,0 +1,133 @@
+/*****************************************************************
+ *   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.dbsync.reverse.filters;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+/**
+ * TableFilter contain at least one IncludeTable always
+ *
+ */
+public class TableFilter {
+
+    private final SortedSet<IncludeTableFilter> includes;
+    private final SortedSet<Pattern> excludes;
+
+    /**
+     * Includes can contain only One includetable
+     *
+     * @param includes
+     * @param excludes
+     */
+    public TableFilter(SortedSet<IncludeTableFilter> includes, SortedSet<Pattern> excludes) {
+        if (includes.isEmpty()) {
+            throw new IllegalArgumentException("TableFilter should contain at least one IncludeTableFilter always " +
+                    "and it is builder responsibility. If you need table filter without includes, use EmptyTableFilter");
+        }
+
+        this.includes = includes;
+        this.excludes = excludes;
+    }
+
+    /**
+     * Return filter for columns in case we should take this table
+     *
+     * @param tableName
+     * @return
+     */
+    public PatternFilter isIncludeTable(String tableName) {
+        IncludeTableFilter include = null;
+        for (IncludeTableFilter p : includes) {
+            if (p.pattern == null || p.pattern.matcher(tableName).matches()) {
+                include = p;
+                break;
+            }
+        }
+
+        if (include == null) {
+            return null;
+        }
+
+        for (Pattern p : excludes) {
+            if (p != null) {
+                if (p.matcher(tableName).matches()) {
+                    return null;
+                }
+            }
+        }
+
+        return include.columnsFilter;
+    }
+
+    public static TableFilter include(String tablePattern) {
+        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
+        includes.add(new IncludeTableFilter(tablePattern == null ? null : tablePattern.replaceAll("%", ".*")));
+
+        return new TableFilter(includes, new TreeSet<Pattern>());
+    }
+
+    public static TableFilter everything() {
+        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
+        includes.add(new IncludeTableFilter(null));
+
+        return new TableFilter(includes, new TreeSet<Pattern>());
+    }
+
+    protected StringBuilder toString(StringBuilder res, String prefix) {
+        res.append(prefix).append("Tables: ").append("\n");
+
+        for (IncludeTableFilter include : includes) {
+            include.toString(res, prefix + "  ");
+        }
+
+        if (!excludes.isEmpty()) {
+            res.append(prefix).append("  ").append(StringUtils.join(excludes, " OR ")).append("\n");
+        }
+
+        return res;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof TableFilter)) {
+            return false;
+        }
+
+        TableFilter that = (TableFilter) o;
+
+        return excludes.equals(that.excludes)
+                && includes.equals(that.includes);
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = includes.hashCode();
+        result = 31 * result + excludes.hashCode();
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/DbType.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/DbType.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/DbType.java
new file mode 100644
index 0000000..9f829ea
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/DbType.java
@@ -0,0 +1,194 @@
+/*****************************************************************
+ *   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.dbsync.reverse.mapper;
+
+import org.apache.cayenne.util.EqualsBuilder;
+import org.apache.cayenne.util.HashCodeBuilder;
+import org.apache.commons.lang.builder.CompareToBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+/**
+ * @since 4.0
+ */
+public class DbType implements Comparable<DbType> {
+
+    private static final Log LOG = LogFactory.getLog(DbType.class);
+
+    public final String jdbc;
+
+    public final Integer length;
+    public final Integer precision;
+    public final Integer scale;
+    public final Boolean notNull;
+
+    public DbType(String jdbc) {
+        this(jdbc, null, null, null, null);
+    }
+
+    public DbType(String jdbc, Integer length, Integer precision, Integer scale, Boolean notNull) {
+        if (isBlank(jdbc)) {
+            throw new IllegalArgumentException("Jdbc type can't be null");
+        }
+        this.jdbc = jdbc;
+
+        this.length = getValidInt(length);
+        this.precision = getValidInt(precision);
+        this.scale = getValidInt(scale);
+        this.notNull = notNull;
+    }
+
+    public String getJdbc() {
+        return jdbc;
+    }
+
+    public Integer getLength() {
+        return length;
+    }
+
+    public Integer getPrecision() {
+        return precision;
+    }
+
+    public Integer getScale() {
+        return scale;
+    }
+
+    public Boolean getNotNull() {
+        return notNull;
+    }
+
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj.getClass() != getClass()) {
+            return false;
+        }
+        DbType rhs = (DbType) obj;
+        return new EqualsBuilder()
+                .append(this.jdbc, rhs.jdbc)
+                .append(this.length, rhs.length)
+                .append(this.precision, rhs.precision)
+                .append(this.scale, rhs.scale)
+                .append(this.notNull, rhs.notNull)
+                .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder()
+                .append(jdbc)
+                .append(length)
+                .append(precision)
+                .append(scale)
+                .append(notNull)
+                .toHashCode();
+    }
+
+
+    @Override
+    public String toString() {
+        String res = jdbc;
+
+        String len = "*";
+        if (isPositive(length)) {
+            len = length.toString();
+        }
+        if (isPositive(precision)) {
+            len = precision.toString();
+        }
+
+        res += " (" + len;
+        if (isPositive(scale)) {
+            res += ", " + scale;
+        }
+        res += ")";
+
+        if (notNull != null && notNull) {
+            res += " NOT NULL";
+        }
+
+        return res;
+    }
+
+    private boolean isPositive(Integer num) {
+        return num != null && num > 0;
+    }
+
+    private Integer getValidInt(Integer num) {
+        if (num == null || num > 0) {
+            return num;
+        }
+
+        LOG.warn("Invalid int value '" + num + "'");
+        return null;
+    }
+
+    /**
+     * Compare by specificity the most specific DbPath should be first in ordered list
+     */
+    @Override
+    public int compareTo(DbType dbType) {
+        return new CompareToBuilder()
+                .append(dbType.jdbc, jdbc)
+                .append(dbType.getSpecificity(), getSpecificity())
+                .append(dbType.length, length)
+                .append(dbType.precision, precision)
+                .append(dbType.scale, scale)
+                .append(dbType.notNull, notNull)
+                .toComparison();
+    }
+
+    private int getSpecificity() {
+        int res = 0;
+        if (isPositive(length)) {
+            res += 100;
+        }
+        if (isPositive(precision)) {
+            res += 100;
+        }
+        if (isPositive(scale)) {
+            res += 10;
+        }
+        if (this.notNull != null) {
+            res += 5;
+        }
+
+        return res;
+    }
+
+    public boolean isCover(DbType type) {
+        return this.jdbc.equals(type.jdbc)
+            && (isCover(length, type.length) || length == null && type.length == null && isCover(precision, type.precision))
+            && isCover(scale, type.scale)
+            && isCover(notNull, type.notNull);
+    }
+
+    private boolean isCover(Object a, Object b) {
+        return a == null || a.equals(b);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/DefaultJdbc2JavaTypeMapper.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/DefaultJdbc2JavaTypeMapper.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/DefaultJdbc2JavaTypeMapper.java
new file mode 100644
index 0000000..0af1690
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/DefaultJdbc2JavaTypeMapper.java
@@ -0,0 +1,282 @@
+/*****************************************************************
+ *   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.dbsync.reverse.mapper;
+
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.util.Util;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.sql.Types;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * @since 4.0.
+ */
+public class DefaultJdbc2JavaTypeMapper implements Jdbc2JavaTypeMapper {
+
+	// Never use "-1" or any other normal integer, since there
+	// is a big chance it is being reserved in java.sql.Types
+	public static final int NOT_DEFINED = Integer.MAX_VALUE;
+
+	// char constants for Java data types
+	public static final String JAVA_LONG = "java.lang.Long";
+	public static final String JAVA_BYTES = "byte[]";
+	public static final String JAVA_BOOLEAN = "java.lang.Boolean";
+	public static final String JAVA_STRING = "java.lang.String";
+	public static final String JAVA_SQLDATE = "java.sql.Date";
+	public static final String JAVA_UTILDATE = "java.util.Date";
+	public static final String JAVA_BIGDECIMAL = "java.math.BigDecimal";
+	public static final String JAVA_DOUBLE = "java.lang.Double";
+	public static final String JAVA_FLOAT = "java.lang.Float";
+	public static final String JAVA_INTEGER = "java.lang.Integer";
+	public static final String JAVA_SHORT = "java.lang.Short";
+	public static final String JAVA_BYTE = "java.lang.Byte";
+	public static final String JAVA_TIME = "java.sql.Time";
+	public static final String JAVA_TIMESTAMP = "java.sql.Timestamp";
+	public static final String JAVA_BLOB = "java.sql.Blob";
+
+	/**
+	 * Keys: java class names, Values: SQL int type definitions from
+	 * java.sql.Types
+	 */
+	private final Map<String, Integer> javaSqlEnum = new HashMap<>();
+
+	private final Map<DbType, String> mapping = new HashMap<>();
+	private final SortedSet<DbType> dbTypes = new TreeSet<>();
+
+	private Map<String, String> classToPrimitive;
+
+	{
+		add(Types.BIGINT, JAVA_LONG);
+		add(Types.BINARY, JAVA_BYTES);
+		add(Types.BIT, JAVA_BOOLEAN);
+		add(Types.BOOLEAN, JAVA_BOOLEAN);
+		add(Types.BLOB, JAVA_BYTES);
+		add(Types.CLOB, JAVA_STRING);
+		add(Types.NCLOB, JAVA_STRING);
+		add(Types.SQLXML, JAVA_STRING);
+		add(Types.CHAR, JAVA_STRING);
+		add(Types.NCHAR, JAVA_STRING);
+		add(Types.DATE, JAVA_UTILDATE);
+		add(Types.DECIMAL, JAVA_BIGDECIMAL);
+		add(Types.DOUBLE, JAVA_DOUBLE);
+		add(Types.FLOAT, JAVA_FLOAT);
+		add(Types.INTEGER, JAVA_INTEGER);
+		add(Types.LONGVARCHAR, JAVA_STRING);
+		add(Types.LONGNVARCHAR, JAVA_STRING);
+		add(Types.LONGVARBINARY, JAVA_BYTES);
+		add(Types.NUMERIC, JAVA_BIGDECIMAL);
+		add(Types.REAL, JAVA_FLOAT);
+		add(Types.SMALLINT, JAVA_SHORT);
+		add(Types.TINYINT, JAVA_SHORT);
+		add(Types.TIME, JAVA_UTILDATE);
+		add(Types.TIMESTAMP, JAVA_UTILDATE);
+		add(Types.VARBINARY, JAVA_BYTES);
+		add(Types.VARCHAR, JAVA_STRING);
+		add(Types.NVARCHAR, JAVA_STRING);
+
+		javaSqlEnum.put(JAVA_LONG, Types.BIGINT);
+		javaSqlEnum.put(JAVA_BYTES, Types.BINARY);
+		javaSqlEnum.put(JAVA_BOOLEAN, Types.BIT);
+		javaSqlEnum.put(JAVA_STRING, Types.VARCHAR);
+		javaSqlEnum.put(JAVA_SQLDATE, Types.DATE);
+		javaSqlEnum.put(JAVA_UTILDATE, Types.DATE);
+		javaSqlEnum.put(JAVA_TIMESTAMP, Types.TIMESTAMP);
+		javaSqlEnum.put(JAVA_BIGDECIMAL, Types.DECIMAL);
+		javaSqlEnum.put(JAVA_DOUBLE, Types.DOUBLE);
+		javaSqlEnum.put(JAVA_FLOAT, Types.FLOAT);
+		javaSqlEnum.put(JAVA_INTEGER, Types.INTEGER);
+		javaSqlEnum.put(JAVA_SHORT, Types.SMALLINT);
+		javaSqlEnum.put(JAVA_BYTE, Types.SMALLINT);
+		javaSqlEnum.put(JAVA_TIME, Types.TIME);
+		javaSqlEnum.put(JAVA_TIMESTAMP, Types.TIMESTAMP);
+
+		// add primitives
+		javaSqlEnum.put("byte", Types.TINYINT);
+		javaSqlEnum.put("int", Types.INTEGER);
+		javaSqlEnum.put("short", Types.SMALLINT);
+		javaSqlEnum.put("char", Types.CHAR);
+		javaSqlEnum.put("double", Types.DOUBLE);
+		javaSqlEnum.put("long", Types.BIGINT);
+		javaSqlEnum.put("float", Types.FLOAT);
+		javaSqlEnum.put("boolean", Types.BIT);
+
+		classToPrimitive = new HashMap<>();
+		classToPrimitive.put(Byte.class.getName(), "byte");
+		classToPrimitive.put(Long.class.getName(), "long");
+		classToPrimitive.put(Double.class.getName(), "double");
+		classToPrimitive.put(Boolean.class.getName(), "boolean");
+		classToPrimitive.put(Float.class.getName(), "float");
+		classToPrimitive.put(Short.class.getName(), "short");
+		classToPrimitive.put(Integer.class.getName(), "int");
+	}
+
+	private Boolean usePrimitives;
+
+	/**
+	 * Returns default java.sql.Types type by the Java type name.
+	 *
+	 * @param className
+	 *            Fully qualified Java Class name.
+	 * @return The SQL type or NOT_DEFINED if no type found.
+	 */
+	public int getJdbcTypeByJava(DbAttribute attribute, String className) {
+		if (className == null) {
+			return NOT_DEFINED;
+		}
+
+		Integer type = javaSqlEnum.get(className);
+		if (type != null) {
+			return type;
+		}
+
+		// try to load a Java class - some nonstandard mappings may work
+		try {
+			return getSqlTypeByJava(attribute, Util.getJavaClass(className));
+		} catch (Throwable th) {
+			return NOT_DEFINED;
+		}
+	}
+
+	public void add(int jdbcType, String java) {
+		add(new DbType(TypesMapping.getSqlNameByType(jdbcType)), java);
+	}
+
+	@Override
+	public void add(DbType type, String java) {
+		mapping.put(type, java);
+		dbTypes.add(type);
+	}
+
+	/**
+	 * Guesses a default JDBC type for the Java class.
+	 *
+	 * @since 1.1
+	 */
+	protected int getSqlTypeByJava(DbAttribute attribute, Class<?> javaClass) {
+		if (javaClass == null) {
+			return NOT_DEFINED;
+		}
+
+		// check standard mapping of class and superclasses
+		Class<?> aClass = javaClass;
+		while (aClass != null) {
+
+			String name;
+
+			if (aClass.isArray()) {
+				name = aClass.getComponentType().getName() + "[]";
+			} else {
+				name = aClass.getName();
+			}
+
+			Object type = javaSqlEnum.get(name);
+			if (type != null) {
+				return ((Number) type).intValue();
+			}
+
+			aClass = aClass.getSuperclass();
+		}
+
+		// check non-standard JDBC types that are still supported by JPA
+		if (javaClass.isArray()) {
+
+			Class<?> elementType = javaClass.getComponentType();
+			if (Character.class.isAssignableFrom(elementType) || Character.TYPE.isAssignableFrom(elementType)) {
+				return Types.VARCHAR;
+			} else if (Byte.class.isAssignableFrom(elementType) || Byte.TYPE.isAssignableFrom(elementType)) {
+				return Types.VARBINARY;
+			}
+		}
+
+		if (Calendar.class.isAssignableFrom(javaClass)) {
+			return Types.TIMESTAMP;
+		}
+
+		if (BigInteger.class.isAssignableFrom(javaClass)) {
+			return Types.BIGINT;
+		}
+
+		if (Serializable.class.isAssignableFrom(javaClass)) {
+			// serializable check should be the last one when all other mapping
+			// attempts failed
+			return Types.VARBINARY;
+		}
+
+		return NOT_DEFINED;
+	}
+
+	/**
+	 * Get the corresponding Java type by its java.sql.Types counterpart. Note
+	 * that this method should be used as a last resort, with explicit mapping
+	 * provided by user used as a first choice, as it can only guess how to map
+	 * certain types, such as NUMERIC, etc.
+	 *
+	 * @return Fully qualified Java type name or null if not found.
+	 */
+	@Override
+	public String getJavaByJdbcType(DbAttribute attribute, int type) {
+		String jdbcType = TypesMapping.getSqlNameByType(type);
+		DbType dbType;
+		if (attribute != null) {
+			dbType = new DbType(jdbcType, attribute.getMaxLength(), attribute.getAttributePrecision(),
+					attribute.getScale(), attribute.isMandatory());
+		} else {
+			dbType = new DbType(jdbcType);
+		}
+
+		String typeName = getJavaByJdbcType(dbType);
+
+		if (usePrimitives != null && usePrimitives) {
+			String primitive = classToPrimitive.get(typeName);
+			if (primitive != null) {
+				return primitive;
+			}
+		}
+
+		return typeName;
+	}
+
+	public String getJavaByJdbcType(DbType type) {
+		for (DbType t : dbTypes) {
+			if (t.isCover(type)) {
+				// because dbTypes sorted by specificity we will take first and
+				// the most specific matching
+				// that applicable for attribute
+				return mapping.get(t);
+			}
+		}
+
+		return null;
+	}
+
+	public Boolean getUsePrimitives() {
+		return usePrimitives;
+	}
+
+	public void setUsePrimitives(Boolean usePrimitives) {
+		this.usePrimitives = usePrimitives;
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/Jdbc2JavaTypeMapper.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/Jdbc2JavaTypeMapper.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/Jdbc2JavaTypeMapper.java
new file mode 100644
index 0000000..71d86d5
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/mapper/Jdbc2JavaTypeMapper.java
@@ -0,0 +1,33 @@
+/*****************************************************************
+ *   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.dbsync.reverse.mapper;
+
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * @since 4.0.
+ */
+public interface Jdbc2JavaTypeMapper {
+
+    String getJavaByJdbcType(DbAttribute attribute, int type);
+
+    int getJdbcTypeByJava(DbAttribute attribute, String className);
+
+    void add(DbType type, String java);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/AddColumnToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/AddColumnToModelIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/AddColumnToModelIT.java
new file mode 100644
index 0000000..30fa47b
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/AddColumnToModelIT.java
@@ -0,0 +1,98 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Types;
+import java.util.List;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+
+public class AddColumnToModelIT extends MergeCase {
+
+    @Test
+    public void testAddColumn() throws Exception {
+        dropTableIfPresent("NEW_TABLE");
+        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);
+
+        map.addDbEntity(dbEntity);
+
+        assertTokensAndExecute(1, 0);
+        assertTokensAndExecute(0, 0);
+
+        ObjEntity objEntity = new ObjEntity("NewTable");
+        objEntity.setDbEntity(dbEntity);
+        ObjAttribute oatr1 = new ObjAttribute("name");
+        oatr1.setDbAttributePath(column2.getName());
+        oatr1.setType("java.lang.String");
+        objEntity.addAttribute(oatr1);
+        map.addObjEntity(objEntity);
+
+        // remove name column
+        objEntity.removeAttribute(oatr1.getName());
+        dbEntity.removeAttribute(column2.getName());
+        assertNull(objEntity.getAttribute(oatr1.getName()));
+        assertEquals(0, objEntity.getAttributes().size());
+        assertNull(dbEntity.getAttribute(column2.getName()));
+
+        List<MergerToken> tokens = createMergeTokens();
+        assertEquals(1, tokens.size());
+        MergerToken token = tokens.get(0);
+        if (token.getDirection().isToDb()) {
+            token = token.createReverse(mergerFactory());
+        }
+        assertTrue(token instanceof AddColumnToModel);
+        execute(token);
+        assertEquals(1, objEntity.getAttributes().size());
+        assertEquals("java.lang.String", objEntity.getAttributes().iterator()
+                .next().getType());
+
+        // clear up
+        map.removeObjEntity(objEntity.getName(), true);
+        map.removeDbEntity(dbEntity.getName(), true);
+        resolver.refreshMappingCache();
+        assertNull(map.getObjEntity(objEntity.getName()));
+        assertNull(map.getDbEntity(dbEntity.getName()));
+        assertFalse(map.getDbEntities().contains(dbEntity));
+
+        assertTokensAndExecute(1, 0);
+        assertTokensAndExecute(0, 0);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/CreateTableToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/CreateTableToModelIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/CreateTableToModelIT.java
new file mode 100644
index 0000000..bac6402
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/CreateTableToModelIT.java
@@ -0,0 +1,97 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+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 java.sql.Types;
+import java.util.List;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+
+public class CreateTableToModelIT extends MergeCase {
+
+	@Test
+	public void testAddTable() throws Exception {
+		dropTableIfPresent("NEW_TABLE");
+		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);
+
+		// for the new entity to the db
+		execute(mergerFactory().createCreateTableToDb(dbEntity));
+
+		List<MergerToken> tokens = createMergeTokens();
+		assertEquals(1, tokens.size());
+		MergerToken token = tokens.get(0);
+		if (token.getDirection().isToDb()) {
+			token = token.createReverse(mergerFactory());
+		}
+		assertTrue(token.getClass().getName(), token instanceof CreateTableToModel);
+
+		execute(token);
+
+		ObjEntity objEntity = null;
+		for (ObjEntity candidate : map.getObjEntities()) {
+			if (dbEntity.getName().equalsIgnoreCase(candidate.getDbEntityName())) {
+				objEntity = candidate;
+				break;
+			}
+		}
+		assertNotNull(objEntity);
+
+		assertEquals(objEntity.getClassName(), map.getDefaultPackage() + "." + objEntity.getName());
+		assertEquals(objEntity.getSuperClassName(), map.getDefaultSuperclass());
+		assertEquals(objEntity.getClientClassName(), map.getDefaultClientPackage() + "." + objEntity.getName());
+		assertEquals(objEntity.getClientSuperClassName(), map.getDefaultClientSuperclass());
+
+		assertEquals(1, objEntity.getAttributes().size());
+		assertEquals("java.lang.String", objEntity.getAttributes().iterator().next().getType());
+
+		// clear up
+		// fix psql case issue
+		map.removeDbEntity(objEntity.getDbEntity().getName(), true);
+		map.removeObjEntity(objEntity.getName(), true);
+		map.removeDbEntity(dbEntity.getName(), true);
+		resolver.refreshMappingCache();
+		assertNull(map.getObjEntity(objEntity.getName()));
+		assertNull(map.getDbEntity(dbEntity.getName()));
+		assertFalse(map.getDbEntities().contains(dbEntity));
+
+		assertTokensAndExecute(1, 0);
+		assertTokensAndExecute(0, 0);
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DbMergerTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DbMergerTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DbMergerTest.java
new file mode 100644
index 0000000..8cb03dd
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DbMergerTest.java
@@ -0,0 +1,261 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.builders.DbEntityBuilder;
+import org.apache.cayenne.dbsync.merge.factory.HSQLMergerTokenFactory;
+import org.apache.cayenne.dbsync.reverse.DbLoaderConfiguration;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dataMap;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbAttr;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbEntity;
+import static org.junit.Assert.assertEquals;
+
+public class DbMergerTest {
+
+    @Test
+    public void testEmptyDataMap() throws Exception {
+        assertEquals(0, dbMerger().createMergeTokens(new ArrayList<DbEntity>(0),
+                new ArrayList<DbEntity>(0), new DbLoaderConfiguration()).size());
+    }
+
+    @Test
+    public void testAddTable() throws Exception {
+        DbEntityBuilder dbEntity =
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt()
+        );
+        DataMap existing = dataMap().with(dbEntity).build();
+
+        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
+                new ArrayList<DbEntity>(0), new DbLoaderConfiguration());
+
+        assertEquals(1, tokens.size());
+        assertEquals(factory().createCreateTableToDb(dbEntity.build()).getTokenValue(),
+                     tokens.get(0).getTokenValue());
+    }
+
+    @Test
+    public void testRemoveTable() throws Exception {
+        DataMap db = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt()
+        )).build();
+
+        List<MergerToken> tokens = dbMerger().createMergeTokens(new ArrayList<DbEntity>(0),
+                db.getDbEntities(), new DbLoaderConfiguration());
+
+        assertEquals(1, tokens.size());
+        assertEquals(factory().createDropTableToDb(db.getDbEntity("table1")).getTokenValue(),
+                     tokens.get(0).getTokenValue());
+    }
+
+    @Test
+    public void testAddColumn() throws Exception {
+        DataMap existing = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt(),
+                dbAttr("attr02").typeInt()
+        )).build();
+
+        DataMap db = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt()
+        )).build();
+
+        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
+                db.getDbEntities(), new DbLoaderConfiguration());
+
+        assertEquals(1, tokens.size());
+
+        DbEntity entity = existing.getDbEntity("table1");
+        assertEquals(factory().createAddColumnToDb(entity, entity.getAttribute("attr02")).getTokenValue(),
+                     tokens.get(0).getTokenValue());
+    }
+
+    @Test
+    public void testAddRelationship() throws Exception {
+        DataMap existing = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt(),
+                dbAttr("attr02").typeInt()),
+
+            dbEntity("table2").attributes(
+                dbAttr("attr01").typeInt().primaryKey(),
+                dbAttr("attr02").typeInt())
+        ).join("rel", "table1.attr01", "table2.attr01")
+         .build();
+
+        DataMap db = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt(),
+                dbAttr("attr02").typeInt()),
+
+            dbEntity("table2").attributes(
+                dbAttr("attr01").typeInt().primaryKey(),
+                dbAttr("attr02").typeInt())
+        )//.join("table1.attr01", "table2.attr01")
+         .build();
+
+
+        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
+                db.getDbEntities(), new DbLoaderConfiguration());
+
+        assertEquals(1, tokens.size());
+
+        DbEntity entity = existing.getDbEntity("table1");
+        assertEquals(factory().createAddRelationshipToDb(entity, entity.getRelationship("rel")).getTokenValue(),
+                     tokens.get(0).getTokenValue());
+    }
+
+    @Test
+    public void testAddRelationship1() throws Exception {
+        DataMap existing = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt(),
+                dbAttr("attr02").typeInt()),
+
+            dbEntity("table2").attributes(
+                dbAttr("attr01").typeInt().primaryKey(),
+                dbAttr("attr02").typeInt().primaryKey(),
+                dbAttr("attr03").typeInt().primaryKey())
+        ).join("rel", "table1.attr01", "table2.attr01")
+         .join("rel1", "table1.attr01", "table2.attr03")
+         .build();
+
+        DataMap db = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt(),
+                dbAttr("attr02").typeInt()),
+
+            dbEntity("table2").attributes(
+                dbAttr("attr01").typeInt().primaryKey(),
+                dbAttr("attr02").typeInt().primaryKey(),
+                dbAttr("attr03").typeInt().primaryKey())
+        ).join("rel", "table1.attr01", "table2.attr02")
+         .join("rel1", "table1.attr01", "table2.attr03")
+         .build();
+
+
+        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
+                db.getDbEntities(), new DbLoaderConfiguration());
+
+        assertEquals(2, tokens.size());
+
+        DbEntity entity = existing.getDbEntity("table1");
+        assertEquals(factory().createDropRelationshipToDb(entity, entity.getRelationship("rel")).getTokenValue(),
+                     tokens.get(0).getTokenValue());
+
+        entity = db.getDbEntity("table1");
+        assertEquals(factory().createAddRelationshipToDb(entity, entity.getRelationship("rel")).getTokenValue(),
+                     tokens.get(0).getTokenValue());
+    }
+
+    @Test
+    public void testRemoveRelationship() throws Exception {
+        DataMap existing = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt(),
+                dbAttr("attr02").typeInt()),
+
+            dbEntity("table2").attributes(
+                dbAttr("attr01").typeInt().primaryKey(),
+                dbAttr("attr02").typeInt())
+        )
+         .build();
+
+        DataMap db = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt(),
+                dbAttr("attr02").typeInt()),
+
+            dbEntity("table2").attributes(
+                dbAttr("attr01").typeInt().primaryKey(),
+                dbAttr("attr02").typeInt())
+        ).join("rel", "table1.attr01", "table2.attr01")
+         .build();
+
+
+        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(), db.getDbEntities(), new DbLoaderConfiguration());
+
+        assertEquals(1, tokens.size());
+
+        DbEntity entity = db.getDbEntity("table1");
+        assertEquals(factory().createDropRelationshipToDb(entity, entity.getRelationship("rel")).getTokenValue(),
+                     tokens.get(0).getTokenValue());
+    }
+
+    @Test
+    public void testRemoveColumn() throws Exception {
+        DataMap existing = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt()
+        )).build();
+
+        DataMap db = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt(),
+                dbAttr("attr02").typeInt()
+        )).build();
+
+        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
+                db.getDbEntities(), new DbLoaderConfiguration());
+
+        assertEquals(1, tokens.size());
+
+        DbEntity entity = db.getDbEntity("table1");
+        assertEquals(factory().createDropColumnToModel(entity, entity.getAttribute("attr02")).getTokenValue(),
+                     tokens.get(0).getTokenValue());
+    }
+
+    @Test
+    public void testNoChanges() throws Exception {
+        DataMap dataMap1 = dataMap().with(
+                dbEntity("table1").attributes(
+                        dbAttr("attr01").typeInt(),
+                        dbAttr("attr02").typeInt(),
+                        dbAttr("attr03").typeInt()
+                )).build();
+
+        DataMap dataMap2 = dataMap().with(
+            dbEntity("table1").attributes(
+                dbAttr("attr01").typeInt(),
+                dbAttr("attr02").typeInt(),
+                dbAttr("attr03").typeInt()
+        )).build();
+
+
+        assertEquals(0, dbMerger().createMergeTokens(dataMap1.getDbEntities(),
+                dataMap2.getDbEntities(), new DbLoaderConfiguration()).size());
+    }
+
+    private DbMerger dbMerger() {
+        return new DbMerger(factory());
+    }
+
+    private HSQLMergerTokenFactory factory() {
+        return new HSQLMergerTokenFactory();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropColumnToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropColumnToModelIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropColumnToModelIT.java
new file mode 100644
index 0000000..f6e0675
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropColumnToModelIT.java
@@ -0,0 +1,235 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.Types;
+import java.util.List;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.junit.Test;
+
+public class DropColumnToModelIT extends MergeCase {
+
+	@Test
+	public void testSimpleColumn() throws Exception {
+		dropTableIfPresent("NEW_TABLE");
+
+		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);
+
+		map.addDbEntity(dbEntity);
+
+		assertTokensAndExecute(1, 0);
+		assertTokensAndExecute(0, 0);
+
+		ObjEntity objEntity = new ObjEntity("NewTable");
+		objEntity.setDbEntity(dbEntity);
+		ObjAttribute oatr1 = new ObjAttribute("name");
+		oatr1.setDbAttributePath(column2.getName());
+		oatr1.setType("java.lang.String");
+		objEntity.addAttribute(oatr1);
+		map.addObjEntity(objEntity);
+
+		// force drop name column in db
+		MergerToken token = mergerFactory().createDropColumnToDb(dbEntity, column2);
+		execute(token);
+
+		List<MergerToken> tokens = createMergeTokens();
+		assertEquals(1, tokens.size());
+		token = tokens.get(0);
+		if (token.getDirection().isToDb()) {
+			token = token.createReverse(mergerFactory());
+		}
+		assertTrue(token instanceof DropColumnToModel);
+		execute(token);
+		assertNull(dbEntity.getAttribute(column2.getName()));
+		assertNull(objEntity.getAttribute(oatr1.getName()));
+
+		// clear up
+		map.removeObjEntity(objEntity.getName(), true);
+		map.removeDbEntity(dbEntity.getName(), true);
+		resolver.refreshMappingCache();
+		assertNull(map.getObjEntity(objEntity.getName()));
+		assertNull(map.getDbEntity(dbEntity.getName()));
+		assertFalse(map.getDbEntities().contains(dbEntity));
+
+		assertTokensAndExecute(1, 0);
+		assertTokensAndExecute(0, 0);
+	}
+
+	@Test
+	public void testRemoveFKColumnWithoutRelationshipInDb() throws Exception {
+		dropTableIfPresent("NEW_TABLE");
+		dropTableIfPresent("NEW_TABLE2");
+
+		assertTokensAndExecute(0, 0);
+
+		DbEntity dbEntity1 = new DbEntity("NEW_TABLE");
+
+		DbAttribute e1col1 = new DbAttribute("ID", Types.INTEGER, dbEntity1);
+		e1col1.setMandatory(true);
+		e1col1.setPrimaryKey(true);
+		dbEntity1.addAttribute(e1col1);
+
+		DbAttribute e1col2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity1);
+		e1col2.setMaxLength(10);
+		e1col2.setMandatory(false);
+		dbEntity1.addAttribute(e1col2);
+
+		map.addDbEntity(dbEntity1);
+
+		DbEntity dbEntity2 = new DbEntity("NEW_TABLE2");
+		DbAttribute e2col1 = new DbAttribute("ID", Types.INTEGER, dbEntity2);
+		e2col1.setMandatory(true);
+		e2col1.setPrimaryKey(true);
+		dbEntity2.addAttribute(e2col1);
+		DbAttribute e2col2 = new DbAttribute("FK", Types.INTEGER, dbEntity2);
+		dbEntity2.addAttribute(e2col2);
+		DbAttribute e2col3 = new DbAttribute("NAME", Types.VARCHAR, dbEntity2);
+		e2col3.setMaxLength(10);
+		dbEntity2.addAttribute(e2col3);
+
+		map.addDbEntity(dbEntity2);
+
+		assertTokensAndExecute(2, 0);
+		assertTokensAndExecute(0, 0);
+
+		// force drop fk column in db
+		execute(mergerFactory().createDropColumnToDb(dbEntity2, e2col2));
+
+		// create db relationships, but do not sync them to db
+		DbRelationship rel1To2 = new DbRelationship("rel1To2");
+		rel1To2.setSourceEntity(dbEntity1);
+		rel1To2.setTargetEntityName(dbEntity2);
+		rel1To2.setToMany(true);
+		rel1To2.addJoin(new DbJoin(rel1To2, e1col1.getName(), e2col2.getName()));
+		dbEntity1.addRelationship(rel1To2);
+		DbRelationship rel2To1 = new DbRelationship("rel2To1");
+		rel2To1.setSourceEntity(dbEntity2);
+		rel2To1.setTargetEntityName(dbEntity1);
+		rel2To1.setToMany(false);
+		rel2To1.addJoin(new DbJoin(rel2To1, e2col2.getName(), e1col1.getName()));
+		dbEntity2.addRelationship(rel2To1);
+		assertSame(rel1To2, rel2To1.getReverseRelationship());
+		assertSame(rel2To1, rel1To2.getReverseRelationship());
+
+		// create ObjEntities
+		ObjEntity objEntity1 = new ObjEntity("NewTable");
+		objEntity1.setDbEntity(dbEntity1);
+		ObjAttribute oatr1 = new ObjAttribute("name");
+		oatr1.setDbAttributePath(e1col2.getName());
+		oatr1.setType("java.lang.String");
+		objEntity1.addAttribute(oatr1);
+		map.addObjEntity(objEntity1);
+		ObjEntity objEntity2 = new ObjEntity("NewTable2");
+		objEntity2.setDbEntity(dbEntity2);
+		ObjAttribute o2a1 = new ObjAttribute("name");
+		o2a1.setDbAttributePath(e2col3.getName());
+		o2a1.setType("java.lang.String");
+		objEntity2.addAttribute(o2a1);
+		map.addObjEntity(objEntity2);
+
+		// create ObjRelationships
+		assertEquals(0, objEntity1.getRelationships().size());
+		assertEquals(0, objEntity2.getRelationships().size());
+		ObjRelationship objRel1To2 = new ObjRelationship("objRel1To2");
+		objRel1To2.addDbRelationship(rel1To2);
+		objRel1To2.setSourceEntity(objEntity1);
+		objRel1To2.setTargetEntityName(objEntity2);
+		objEntity1.addRelationship(objRel1To2);
+		ObjRelationship objRel2To1 = new ObjRelationship("objRel2To1");
+		objRel2To1.addDbRelationship(rel2To1);
+		objRel2To1.setSourceEntity(objEntity2);
+		objRel2To1.setTargetEntityName(objEntity1);
+		objEntity2.addRelationship(objRel2To1);
+		assertEquals(1, objEntity1.getRelationships().size());
+		assertEquals(1, objEntity2.getRelationships().size());
+		assertSame(objRel1To2, objRel2To1.getReverseRelationship());
+		assertSame(objRel2To1, objRel1To2.getReverseRelationship());
+
+		// try do use the merger to remove the column and relationship in the
+		// model
+		List<MergerToken> tokens = createMergeTokens();
+		assertTokens(tokens, 2, 0);
+		// TODO: reversing the following two tokens should also reverse the
+		// order
+		MergerToken token0 = tokens.get(0).createReverse(mergerFactory());
+		MergerToken token1 = tokens.get(1).createReverse(mergerFactory());
+		if (!(token0 instanceof DropRelationshipToModel && token1 instanceof DropColumnToModel || token1 instanceof DropRelationshipToModel
+				&& token0 instanceof DropColumnToModel)) {
+			fail();
+		}
+		// do not execute DropRelationshipToModel, only DropColumnToModel.
+		if (token1 instanceof DropColumnToModel) {
+			execute(token1);
+		} else {
+			execute(token0);
+		}
+
+		// check after merging
+		assertNull(dbEntity2.getAttribute(e2col2.getName()));
+		assertEquals(0, dbEntity1.getRelationships().size());
+		assertEquals(0, dbEntity2.getRelationships().size());
+		assertEquals(0, objEntity1.getRelationships().size());
+		assertEquals(0, objEntity2.getRelationships().size());
+
+		// clear up
+
+		dbEntity1.removeRelationship(rel1To2.getName());
+		dbEntity2.removeRelationship(rel2To1.getName());
+		map.removeObjEntity(objEntity1.getName(), true);
+		map.removeDbEntity(dbEntity1.getName(), true);
+		map.removeObjEntity(objEntity2.getName(), true);
+		map.removeDbEntity(dbEntity2.getName(), true);
+		resolver.refreshMappingCache();
+		assertNull(map.getObjEntity(objEntity1.getName()));
+		assertNull(map.getDbEntity(dbEntity1.getName()));
+		assertNull(map.getObjEntity(objEntity2.getName()));
+		assertNull(map.getDbEntity(dbEntity2.getName()));
+		assertFalse(map.getDbEntities().contains(dbEntity1));
+		assertFalse(map.getDbEntities().contains(dbEntity2));
+
+		assertTokensAndExecute(2, 0);
+		assertTokensAndExecute(0, 0);
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropRelationshipToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropRelationshipToModelIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropRelationshipToModelIT.java
new file mode 100644
index 0000000..cf2ec3d
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropRelationshipToModelIT.java
@@ -0,0 +1,188 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.sql.Types;
+import java.util.List;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.junit.Test;
+
+public class DropRelationshipToModelIT extends MergeCase {
+
+	@Test
+	public void testForeignKey() throws Exception {
+		dropTableIfPresent("NEW_TABLE");
+		dropTableIfPresent("NEW_TABLE2");
+
+		assertTokensAndExecute(0, 0);
+
+		DbEntity dbEntity1 = new DbEntity("NEW_TABLE");
+
+		DbAttribute e1col1 = new DbAttribute("ID", Types.INTEGER, dbEntity1);
+		e1col1.setMandatory(true);
+		e1col1.setPrimaryKey(true);
+		dbEntity1.addAttribute(e1col1);
+
+		DbAttribute e1col2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity1);
+		e1col2.setMaxLength(10);
+		e1col2.setMandatory(false);
+		dbEntity1.addAttribute(e1col2);
+
+		map.addDbEntity(dbEntity1);
+
+		DbEntity dbEntity2 = new DbEntity("NEW_TABLE2");
+		DbAttribute e2col1 = new DbAttribute("ID", Types.INTEGER, dbEntity2);
+		e2col1.setMandatory(true);
+		e2col1.setPrimaryKey(true);
+		dbEntity2.addAttribute(e2col1);
+		DbAttribute e2col2 = new DbAttribute("FK", Types.INTEGER, dbEntity2);
+		dbEntity2.addAttribute(e2col2);
+		DbAttribute e2col3 = new DbAttribute("NAME", Types.VARCHAR, dbEntity2);
+		e2col3.setMaxLength(10);
+		dbEntity2.addAttribute(e2col3);
+
+		map.addDbEntity(dbEntity2);
+
+		// create db relationships
+		DbRelationship rel1To2 = new DbRelationship("rel1To2");
+		rel1To2.setSourceEntity(dbEntity1);
+		rel1To2.setTargetEntityName(dbEntity2);
+		rel1To2.setToMany(true);
+		rel1To2.addJoin(new DbJoin(rel1To2, e1col1.getName(), e2col2.getName()));
+		dbEntity1.addRelationship(rel1To2);
+		DbRelationship rel2To1 = new DbRelationship("rel2To1");
+		rel2To1.setSourceEntity(dbEntity2);
+		rel2To1.setTargetEntityName(dbEntity1);
+		rel2To1.setToMany(false);
+		rel2To1.addJoin(new DbJoin(rel2To1, e2col2.getName(), e1col1.getName()));
+		dbEntity2.addRelationship(rel2To1);
+		assertSame(rel1To2, rel2To1.getReverseRelationship());
+		assertSame(rel2To1, rel1To2.getReverseRelationship());
+
+		assertTokensAndExecute(4, 0);
+		assertTokensAndExecute(0, 0);
+
+		// create ObjEntities
+		ObjEntity objEntity1 = new ObjEntity("NewTable");
+		objEntity1.setDbEntity(dbEntity1);
+		ObjAttribute oatr1 = new ObjAttribute("name");
+		oatr1.setDbAttributePath(e1col2.getName());
+		oatr1.setType("java.lang.String");
+		objEntity1.addAttribute(oatr1);
+		map.addObjEntity(objEntity1);
+		ObjEntity objEntity2 = new ObjEntity("NewTable2");
+		objEntity2.setDbEntity(dbEntity2);
+		ObjAttribute o2a1 = new ObjAttribute("name");
+		o2a1.setDbAttributePath(e2col3.getName());
+		o2a1.setType("java.lang.String");
+		objEntity2.addAttribute(o2a1);
+		map.addObjEntity(objEntity2);
+
+		// create ObjRelationships
+		assertEquals(0, objEntity1.getRelationships().size());
+		assertEquals(0, objEntity2.getRelationships().size());
+		ObjRelationship objRel1To2 = new ObjRelationship("objRel1To2");
+		objRel1To2.addDbRelationship(rel1To2);
+		objRel1To2.setSourceEntity(objEntity1);
+		objRel1To2.setTargetEntityName(objEntity2);
+		objEntity1.addRelationship(objRel1To2);
+		ObjRelationship objRel2To1 = new ObjRelationship("objRel2To1");
+		objRel2To1.addDbRelationship(rel2To1);
+		objRel2To1.setSourceEntity(objEntity2);
+		objRel2To1.setTargetEntityName(objEntity1);
+		objEntity2.addRelationship(objRel2To1);
+		assertEquals(1, objEntity1.getRelationships().size());
+		assertEquals(1, objEntity2.getRelationships().size());
+		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();
+        /**
+         * Add Relationship NEW_TABLE->NEW_TABLE2 To Model
+         * Drop Relationship NEW_TABLE2->NEW_TABLE To DB
+         * Drop Column NEW_TABLE2.FK To DB
+         * */
+        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);
+
+		// try do use the merger to remove the relationship in the model
+		tokens = createMergeTokens();
+		assertTokens(tokens, 2, 0);
+		// TODO: reversing the following two tokens should also reverse the
+		// order
+		MergerToken token0 = tokens.get(0).createReverse(mergerFactory());
+		MergerToken token1 = tokens.get(1).createReverse(mergerFactory());
+		if (!(token0 instanceof DropRelationshipToModel && token1 instanceof DropColumnToModel || token1 instanceof DropRelationshipToModel
+				&& token0 instanceof DropColumnToModel)) {
+			fail();
+		}
+		execute(token0);
+		execute(token1);
+
+		// check after merging
+		assertNull(dbEntity2.getAttribute(e2col2.getName()));
+		assertEquals(0, dbEntity1.getRelationships().size());
+		assertEquals(0, dbEntity2.getRelationships().size());
+		assertEquals(0, objEntity1.getRelationships().size());
+		assertEquals(0, objEntity2.getRelationships().size());
+
+		// clear up
+		dbEntity1.removeRelationship(rel1To2.getName());
+		dbEntity2.removeRelationship(rel2To1.getName());
+		map.removeObjEntity(objEntity1.getName(), true);
+		map.removeDbEntity(dbEntity1.getName(), true);
+		map.removeObjEntity(objEntity2.getName(), true);
+		map.removeDbEntity(dbEntity2.getName(), true);
+		resolver.refreshMappingCache();
+		assertNull(map.getObjEntity(objEntity1.getName()));
+		assertNull(map.getDbEntity(dbEntity1.getName()));
+		assertNull(map.getObjEntity(objEntity2.getName()));
+		assertNull(map.getDbEntity(dbEntity2.getName()));
+		assertFalse(map.getDbEntities().contains(dbEntity1));
+		assertFalse(map.getDbEntities().contains(dbEntity2));
+
+		assertTokensAndExecute(2, 0);
+		assertTokensAndExecute(0, 0);
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropTableToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropTableToModelIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropTableToModelIT.java
new file mode 100644
index 0000000..5b1744a
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/DropTableToModelIT.java
@@ -0,0 +1,94 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Types;
+import java.util.List;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+
+public class DropTableToModelIT extends MergeCase {
+
+	@Test
+	public void testDropTable() throws Exception {
+		dropTableIfPresent("NEW_TABLE");
+		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);
+
+		map.addDbEntity(dbEntity);
+
+		assertTokensAndExecute(1, 0);
+		assertTokensAndExecute(0, 0);
+
+		ObjEntity objEntity = new ObjEntity("NewTable");
+		objEntity.setDbEntity(dbEntity);
+		ObjAttribute oatr1 = new ObjAttribute("name");
+		oatr1.setDbAttributePath(column2.getName());
+		oatr1.setType("java.lang.String");
+		objEntity.addAttribute(oatr1);
+		map.addObjEntity(objEntity);
+
+		// force drop table in db
+		MergerToken token = mergerFactory().createDropTableToDb(dbEntity);
+		execute(token);
+
+		List<MergerToken> tokens = createMergeTokens();
+		assertEquals(1, tokens.size());
+		token = tokens.get(0);
+		if (token.getDirection().isToDb()) {
+			token = token.createReverse(mergerFactory());
+		}
+		assertTrue(token instanceof DropTableToModel);
+		execute(token);
+		resolver.refreshMappingCache();
+		assertNull(map.getDbEntity(dbEntity.getName()));
+		assertNull(map.getObjEntity(objEntity.getName()));
+
+		// clear up
+		map.removeObjEntity(objEntity.getName(), true);
+		map.removeDbEntity(dbEntity.getName(), true);
+		resolver.refreshMappingCache();
+		assertNull(map.getObjEntity(objEntity.getName()));
+		assertNull(map.getDbEntity(dbEntity.getName()));
+		assertFalse(map.getDbEntities().contains(dbEntity));
+
+		assertTokensAndExecute(0, 0);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/EntityMergeSupportIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/EntityMergeSupportIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/EntityMergeSupportIT.java
new file mode 100644
index 0000000..d63c6a4
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/EntityMergeSupportIT.java
@@ -0,0 +1,102 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.DeleteRule;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+
+import java.sql.Types;
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class EntityMergeSupportIT extends MergeCase {
+
+	@Test
+	public void testMerging() {
+		DbEntity dbEntity1 = new DbEntity("NEW_TABLE");
+
+		DbAttribute e1col1 = new DbAttribute("ID", Types.INTEGER, dbEntity1);
+		e1col1.setMandatory(true);
+		e1col1.setPrimaryKey(true);
+		dbEntity1.addAttribute(e1col1);
+
+		DbAttribute e1col2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity1);
+		e1col2.setMaxLength(10);
+		e1col2.setMandatory(false);
+		dbEntity1.addAttribute(e1col2);
+
+		map.addDbEntity(dbEntity1);
+
+		DbEntity dbEntity2 = new DbEntity("NEW_TABLE2");
+		DbAttribute e2col1 = new DbAttribute("ID", Types.INTEGER, dbEntity2);
+		e2col1.setMandatory(true);
+		e2col1.setPrimaryKey(true);
+		dbEntity2.addAttribute(e2col1);
+		DbAttribute e2col2 = new DbAttribute("FK", Types.INTEGER, dbEntity2);
+		dbEntity2.addAttribute(e2col2);
+
+		map.addDbEntity(dbEntity2);
+
+		// create db relationships
+		DbRelationship rel1To2 = new DbRelationship("rel1To2");
+		rel1To2.setSourceEntity(dbEntity1);
+		rel1To2.setTargetEntityName(dbEntity2);
+		rel1To2.setToMany(true);
+		rel1To2.addJoin(new DbJoin(rel1To2, e1col1.getName(), e2col2.getName()));
+		dbEntity1.addRelationship(rel1To2);
+		DbRelationship rel2To1 = new DbRelationship("rel2To1");
+		rel2To1.setSourceEntity(dbEntity2);
+		rel2To1.setTargetEntityName(dbEntity1);
+		rel2To1.setToMany(false);
+		rel2To1.addJoin(new DbJoin(rel2To1, e2col2.getName(), e1col1.getName()));
+		dbEntity2.addRelationship(rel2To1);
+		assertSame(rel1To2, rel2To1.getReverseRelationship());
+		assertSame(rel2To1, rel1To2.getReverseRelationship());
+
+		ObjEntity objEntity1 = new ObjEntity("NewTable");
+		objEntity1.setDbEntity(dbEntity1);
+		map.addObjEntity(objEntity1);
+
+		ObjEntity objEntity2 = new ObjEntity("NewTable2");
+		objEntity2.setDbEntity(dbEntity2);
+		map.addObjEntity(objEntity2);
+
+		assertTrue(new EntityMergeSupport(map).synchronizeWithDbEntities(Arrays.asList(objEntity1, objEntity2)));
+		assertNotNull(objEntity1.getAttribute("name"));
+		assertNotNull(objEntity1.getRelationship("rel1To2"));
+		assertNotNull(objEntity2.getRelationship("rel2To1"));
+
+		assertEquals(objEntity1.getRelationship("rel1To2").getDeleteRule(), DeleteRule.DEFAULT_DELETE_RULE_TO_MANY);
+		assertEquals(objEntity2.getRelationship("rel2To1").getDeleteRule(), DeleteRule.DEFAULT_DELETE_RULE_TO_ONE);
+
+		map.removeObjEntity(objEntity2.getName());
+		map.removeObjEntity(objEntity1.getName());
+		map.removeDbEntity(dbEntity2.getName());
+		map.removeDbEntity(dbEntity1.getName());
+	}
+}


[10/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
new file mode 100644
index 0000000..c2357b0
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
@@ -0,0 +1,209 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
+import org.apache.cayenne.dbsync.reverse.DbLoaderConfiguration;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.dbsync.unit.DbSyncCase;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.unit.UnitDbAdapter;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Before;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public abstract class MergeCase extends DbSyncCase {
+
+    @Inject
+    protected EntityResolver resolver;
+    @Inject
+    protected DataNode node;
+    protected DataMap map;
+    private Log logger = LogFactory.getLog(MergeCase.class);
+    @Inject
+    private DBHelper dbHelper;
+    @Inject
+    private ServerRuntime runtime;
+    @Inject
+    private UnitDbAdapter accessStackAdapter;
+    @Inject
+    private ServerCaseDataSourceFactory dataSourceFactory;
+
+    @Override
+    public void cleanUpDB() throws Exception {
+        dbHelper.update("ARTGROUP").set("PARENT_GROUP_ID", null, Types.INTEGER).execute();
+        super.cleanUpDB();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+
+        // this map can't be safely modified in this test, as it is reset by DI
+        // container
+        // on every test
+        map = runtime.getDataDomain().getDataMap("testmap");
+
+        filterDataMap();
+
+        List<MergerToken> tokens = createMergeTokens();
+        execute(tokens);
+
+        assertTokensAndExecute(0, 0);
+    }
+
+    protected DbMerger createMerger() {
+        return createMerger(null);
+    }
+
+    protected DbMerger createMerger(ValueForNullProvider valueForNullProvider) {
+        return new DbMerger(mergerFactory(), valueForNullProvider);
+    }
+
+    protected List<MergerToken> createMergeTokens() {
+        DbLoaderConfiguration loaderConfiguration = new DbLoaderConfiguration();
+        loaderConfiguration.setFiltersConfig(FiltersConfig.create(null, null,
+                TableFilter.include("ARTIST|GALLERY|PAINTING|NEW_TABLE2?"), PatternFilter.INCLUDE_NOTHING));
+
+        return createMerger().createMergeTokens(node.getDataSource(), node.getAdapter(), map, loaderConfiguration);
+    }
+
+    /**
+     * Remote binary pk {@link DbEntity} for {@link DbAdapter} not supporting
+     * that and so on.
+     */
+    private void filterDataMap() {
+        // copied from AbstractAccessStack.dbEntitiesInInsertOrder
+        boolean excludeBinPK = accessStackAdapter.supportsBinaryPK();
+
+        if (!excludeBinPK) {
+            return;
+        }
+
+        List<DbEntity> entitiesToRemove = new ArrayList<DbEntity>();
+
+        for (DbEntity ent : map.getDbEntities()) {
+            for (DbAttribute attr : ent.getAttributes()) {
+                // check for BIN PK or FK to BIN Pk
+                if (attr.getType() == Types.BINARY || attr.getType() == Types.VARBINARY
+                        || attr.getType() == Types.LONGVARBINARY) {
+
+                    if (attr.isPrimaryKey() || attr.isForeignKey()) {
+                        entitiesToRemove.add(ent);
+                        break;
+                    }
+                }
+            }
+        }
+
+        for (DbEntity e : entitiesToRemove) {
+            map.removeDbEntity(e.getName(), true);
+        }
+    }
+
+    protected void execute(List<MergerToken> tokens) {
+        MergerContext mergerContext = MergerContext.builder(map).dataNode(node).build();
+        for (MergerToken tok : tokens) {
+            tok.execute(mergerContext);
+        }
+    }
+
+    protected void execute(MergerToken token) throws Exception {
+        MergerContext mergerContext = MergerContext.builder(map).dataNode(node).build();
+        token.execute(mergerContext);
+    }
+
+    private void executeSql(String sql) throws Exception {
+
+        try (Connection conn = dataSourceFactory.getSharedDataSource().getConnection();) {
+
+            try (Statement st = conn.createStatement();) {
+                st.execute(sql);
+            }
+        }
+    }
+
+    protected void assertTokens(List<MergerToken> tokens, int expectedToDb, int expectedToModel) {
+        int actualToDb = 0;
+        int actualToModel = 0;
+        for (MergerToken token : tokens) {
+            if (token.getDirection().isToDb()) {
+                actualToDb++;
+            } else if (token.getDirection().isToModel()) {
+                actualToModel++;
+            }
+        }
+
+        assertEquals("tokens to db", expectedToDb, actualToDb);
+        assertEquals("tokens to model", expectedToModel, actualToModel);
+    }
+
+    protected void assertTokensAndExecute(int expectedToDb, int expectedToModel) {
+        List<MergerToken> tokens = createMergeTokens();
+        assertTokens(tokens, expectedToDb, expectedToModel);
+        execute(tokens);
+    }
+
+    protected MergerTokenFactory mergerFactory() {
+        return runtime.getInjector().getInstance(MergerTokenFactoryProvider.class).get(node.getAdapter());
+    }
+
+    protected void dropTableIfPresent(String tableName) throws Exception {
+
+        // must have a dummy datamap for the dummy table for the downstream code
+        // to work
+        DataMap map = new DataMap("dummy");
+        map.setQuotingSQLIdentifiers(map.isQuotingSQLIdentifiers());
+        DbEntity entity = new DbEntity(tableName);
+        map.addDbEntity(entity);
+
+        AbstractToDbToken t = (AbstractToDbToken) mergerFactory().createDropTableToDb(entity);
+
+        for (String sql : t.createSql(node.getAdapter())) {
+
+            try {
+                executeSql(sql);
+            } catch (Exception e) {
+                logger.info("Exception dropping table " + tableName + ", probably abscent..");
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergerFactoryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergerFactoryIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergerFactoryIT.java
new file mode 100644
index 0000000..1fe7da7
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergerFactoryIT.java
@@ -0,0 +1,310 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.sql.Types;
+
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+
+public class MergerFactoryIT extends MergeCase {
+
+    @Inject
+    private DataContext context;
+
+    @Test
+    public void testAddAndDropColumnToDb() throws Exception {
+        DbEntity dbEntity = map.getDbEntity("PAINTING");
+        assertNotNull(dbEntity);
+
+        // create and add new column to model and db
+        DbAttribute column = new DbAttribute("NEWCOL1", Types.VARCHAR, dbEntity);
+
+        column.setMandatory(false);
+        column.setMaxLength(10);
+        dbEntity.addAttribute(column);
+        assertTokensAndExecute(1, 0);
+
+        // try merge once more to check that is was merged
+        assertTokensAndExecute(0, 0);
+
+        // remove it from model and db
+        dbEntity.removeAttribute(column.getName());
+        assertTokensAndExecute(1, 0);
+        assertTokensAndExecute(0, 0);
+    }
+
+    @Test
+    public void testChangeVarcharSizeToDb() throws Exception {
+        DbEntity dbEntity = map.getDbEntity("PAINTING");
+        assertNotNull(dbEntity);
+
+        // create and add new column to model and db
+        DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
+
+        column.setMandatory(false);
+        column.setMaxLength(10);
+        dbEntity.addAttribute(column);
+        assertTokensAndExecute(1, 0);
+
+        // check that is was merged
+        assertTokensAndExecute(0, 0);
+
+        // change size
+        column.setMaxLength(20);
+
+        // merge to db
+        assertTokensAndExecute(1, 0);
+
+        // check that is was merged
+        assertTokensAndExecute(0, 0);
+
+        // clean up
+        dbEntity.removeAttribute(column.getName());
+        assertTokensAndExecute(1, 0);
+        assertTokensAndExecute(0, 0);
+    }
+
+    @Test
+    public void testMultipleTokensToDb() throws Exception {
+        DbEntity dbEntity = map.getDbEntity("PAINTING");
+        assertNotNull(dbEntity);
+
+        DbAttribute column1 = new DbAttribute("NEWCOL3", Types.VARCHAR, dbEntity);
+        column1.setMandatory(false);
+        column1.setMaxLength(10);
+        dbEntity.addAttribute(column1);
+        DbAttribute column2 = new DbAttribute("NEWCOL4", Types.VARCHAR, dbEntity);
+        column2.setMandatory(false);
+        column2.setMaxLength(10);
+        dbEntity.addAttribute(column2);
+
+        assertTokensAndExecute(2, 0);
+
+        // check that is was merged
+        assertTokensAndExecute(0, 0);
+
+        // change size
+        column1.setMaxLength(20);
+        column2.setMaxLength(30);
+
+        // merge to db
+        assertTokensAndExecute(2, 0);
+
+        // check that is was merged
+        assertTokensAndExecute(0, 0);
+
+        // clean up
+        dbEntity.removeAttribute(column1.getName());
+        dbEntity.removeAttribute(column2.getName());
+        assertTokensAndExecute(2, 0);
+        assertTokensAndExecute(0, 0);
+    }
+
+    @Test
+    public void testAddTableToDb() throws Exception {
+        dropTableIfPresent("NEW_TABLE");
+
+        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);
+
+        map.addDbEntity(dbEntity);
+
+        assertTokensAndExecute(1, 0);
+        assertTokensAndExecute(0, 0);
+
+        ObjEntity objEntity = new ObjEntity("NewTable");
+        objEntity.setDbEntity(dbEntity);
+        ObjAttribute oatr1 = new ObjAttribute("name");
+        oatr1.setDbAttributePath(column2.getName());
+        oatr1.setType("java.lang.String");
+        objEntity.addAttribute(oatr1);
+        map.addObjEntity(objEntity);
+
+        for (int i = 0; i < 5; i++) {
+            CayenneDataObject dao = (CayenneDataObject) context.newObject(objEntity
+                    .getName());
+            dao.writeProperty(oatr1.getName(), "test " + i);
+        }
+        context.commitChanges();
+
+        // clear up
+        map.removeObjEntity(objEntity.getName(), true);
+        map.removeDbEntity(dbEntity.getName(), true);
+        resolver.refreshMappingCache();
+        assertNull(map.getObjEntity(objEntity.getName()));
+        assertNull(map.getDbEntity(dbEntity.getName()));
+        assertFalse(map.getDbEntities().contains(dbEntity));
+
+        assertTokensAndExecute(1, 0);
+        assertTokensAndExecute(0, 0);
+    }
+
+    @Test
+    public void testAddForeignKeyWithTable() throws Exception {
+        dropTableIfPresent("NEW_TABLE");
+
+        assertTokensAndExecute(0, 0);
+
+        DbEntity dbEntity = new DbEntity("NEW_TABLE");
+
+        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);
+
+        DbEntity artistDbEntity = map.getDbEntity("ARTIST");
+        assertNotNull(artistDbEntity);
+
+        // relation from new_table to artist
+        DbRelationship r1 = new DbRelationship("toArtistR1");
+        r1.setSourceEntity(dbEntity);
+        r1.setTargetEntityName(artistDbEntity);
+        r1.setToMany(false);
+        r1.addJoin(new DbJoin(r1, "ARTIST_ID", "ARTIST_ID"));
+        dbEntity.addRelationship(r1);
+
+        // relation from artist to new_table
+        DbRelationship r2 = new DbRelationship("toNewTableR2");
+        r2.setSourceEntity(artistDbEntity);
+        r2.setTargetEntityName(dbEntity);
+        r2.setToMany(true);
+        r2.addJoin(new DbJoin(r2, "ARTIST_ID", "ARTIST_ID"));
+        artistDbEntity.addRelationship(r2);
+
+        assertTokensAndExecute(2, 0);
+        assertTokensAndExecute(0, 0);
+
+        // remove relationships
+        dbEntity.removeRelationship(r1.getName());
+        artistDbEntity.removeRelationship(r2.getName());
+        resolver.refreshMappingCache();
+        /*
+         * Db -Rel 'toArtistR1' - NEW_TABLE 1 -> 1 ARTIST"
+r2 =     * Db -Rel 'toNewTableR2' - ARTIST 1 -> * NEW_TABLE"
+         * */
+        assertTokensAndExecute(1, 1);
+        assertTokensAndExecute(0, 0);
+
+        // clear up
+        // map.removeObjEntity(objEntity.getName(), true);
+        map.removeDbEntity(dbEntity.getName(), true);
+        resolver.refreshMappingCache();
+        // assertNull(map.getObjEntity(objEntity.getName()));
+        assertNull(map.getDbEntity(dbEntity.getName()));
+        assertFalse(map.getDbEntities().contains(dbEntity));
+
+        assertTokensAndExecute(1, 0);
+        assertTokensAndExecute(0, 0);
+    }
+
+    @Test
+    public void testAddForeignKeyAfterTable() throws Exception {
+        dropTableIfPresent("NEW_TABLE");
+
+        assertTokensAndExecute(0, 0);
+
+        DbEntity dbEntity = new DbEntity("NEW_TABLE");
+        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);
+
+        DbEntity artistDbEntity = map.getDbEntity("ARTIST");
+        assertNotNull(artistDbEntity);
+
+        assertTokensAndExecute(1, 0);
+        assertTokensAndExecute(0, 0);
+
+        // relation from new_table to artist
+        DbRelationship r1 = new DbRelationship("toArtistR1");
+        r1.setSourceEntity(dbEntity);
+        r1.setTargetEntityName(artistDbEntity);
+        r1.setToMany(false);
+        r1.addJoin(new DbJoin(r1, "ARTIST_ID", "ARTIST_ID"));
+        dbEntity.addRelationship(r1);
+
+        // relation from artist to new_table
+        DbRelationship r2 = new DbRelationship("toNewTableR2");
+        r2.setSourceEntity(artistDbEntity);
+        r2.setTargetEntityName(dbEntity);
+        r2.setToMany(true);
+        r2.addJoin(new DbJoin(r2, "ARTIST_ID", "ARTIST_ID"));
+        artistDbEntity.addRelationship(r2);
+
+        assertTokensAndExecute(1, 0);
+        assertTokensAndExecute(0, 0);
+
+        // remove relationships
+        dbEntity.removeRelationship(r1.getName());
+        artistDbEntity.removeRelationship(r2.getName());
+        resolver.refreshMappingCache();
+        /*
+        * Add Relationship ARTIST->NEW_TABLE To Model
+        * Drop Relationship NEW_TABLE->ARTIST To DB
+        * */
+        assertTokensAndExecute(1, 1);
+        assertTokensAndExecute(0, 0);
+
+        // clear up
+        // map.removeObjEntity(objEntity.getName(), true);
+        map.removeDbEntity(dbEntity.getName(), true);
+        resolver.refreshMappingCache();
+        // assertNull(map.getObjEntity(objEntity.getName()));
+        assertNull(map.getDbEntity(dbEntity.getName()));
+        assertFalse(map.getDbEntities().contains(dbEntity));
+
+        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/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDbIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDbIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDbIT.java
new file mode 100644
index 0000000..6391ad0
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDbIT.java
@@ -0,0 +1,66 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.sql.Types;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Test;
+
+public class SetAllowNullToDbIT extends MergeCase {
+
+	@Test
+	public void test() throws Exception {
+		DbEntity dbEntity = map.getDbEntity("PAINTING");
+		assertNotNull(dbEntity);
+
+		// create and add new column to model and db
+		DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
+
+		try {
+
+			column.setMandatory(true);
+			column.setMaxLength(10);
+			dbEntity.addAttribute(column);
+			assertTokensAndExecute(2, 0);
+
+			// check that is was merged
+			assertTokensAndExecute(0, 0);
+
+			// set null
+			column.setMandatory(false);
+
+			// merge to db
+			assertTokensAndExecute(1, 0);
+
+			// check that is was merged
+			assertTokensAndExecute(0, 0);
+
+			// clean up
+		} finally {
+			dbEntity.removeAttribute(column.getName());
+			assertTokensAndExecute(1, 0);
+			assertTokensAndExecute(0, 0);
+		}
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetNotNullToDbIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetNotNullToDbIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetNotNullToDbIT.java
new file mode 100644
index 0000000..508d0f8
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetNotNullToDbIT.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.dbsync.merge;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.sql.Types;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Test;
+
+public class SetNotNullToDbIT extends MergeCase {
+
+	@Test
+	public void test() throws Exception {
+		DbEntity dbEntity = map.getDbEntity("PAINTING");
+		assertNotNull(dbEntity);
+
+		// create and add new column to model and db
+		DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
+
+		column.setMandatory(false);
+		column.setMaxLength(10);
+		dbEntity.addAttribute(column);
+		assertTokensAndExecute(1, 0);
+
+		// check that is was merged
+		assertTokensAndExecute(0, 0);
+
+		// set not null
+		column.setMandatory(true);
+
+		// merge to db
+		assertTokensAndExecute(1, 0);
+
+		// check that is was merged
+		assertTokensAndExecute(0, 0);
+
+		// clean up
+		dbEntity.removeAttribute(column.getName());
+		assertTokensAndExecute(1, 0);
+		assertTokensAndExecute(0, 0);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDbIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDbIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDbIT.java
new file mode 100644
index 0000000..3b513e7
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDbIT.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.sql.Types;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Test;
+
+public class SetPrimaryKeyToDbIT extends MergeCase {
+
+	@Test
+	public void test() throws Exception {
+		dropTableIfPresent("NEW_TABLE");
+		assertTokensAndExecute(0, 0);
+
+		DbEntity dbEntity1 = new DbEntity("NEW_TABLE");
+
+		DbAttribute e1col1 = new DbAttribute("ID1", Types.INTEGER, dbEntity1);
+		e1col1.setMandatory(true);
+		e1col1.setPrimaryKey(true);
+		dbEntity1.addAttribute(e1col1);
+		map.addDbEntity(dbEntity1);
+
+		assertTokensAndExecute(1, 0);
+		assertTokensAndExecute(0, 0);
+
+		DbAttribute e1col2 = new DbAttribute("ID2", Types.INTEGER, dbEntity1);
+		e1col2.setMandatory(true);
+		dbEntity1.addAttribute(e1col2);
+
+		assertTokensAndExecute(2, 0);
+		assertTokensAndExecute(0, 0);
+
+		e1col1.setPrimaryKey(false);
+		e1col2.setPrimaryKey(true);
+
+		assertTokensAndExecute(1, 0);
+		assertTokensAndExecute(0, 0);
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensReversTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensReversTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensReversTest.java
new file mode 100644
index 0000000..b23128c
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensReversTest.java
@@ -0,0 +1,89 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.HSQLMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbAttr;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbEntity;
+
+/**
+ * @since 4.0.
+ */
+public class TokensReversTest {
+
+    @Test
+    public void testReverses() {
+        DbAttribute attr = dbAttr().build();
+        DbEntity entity = dbEntity().attributes(attr).build();
+        DbRelationship rel = new DbRelationship("rel");
+        rel.setSourceEntity(entity);
+        rel.addJoin(new DbJoin(rel, attr.getName(), "dontKnow"));
+
+        test(factory().createAddColumnToDb(entity, attr));
+        test(factory().createAddColumnToModel(entity, attr));
+        test(factory().createDropColumnToDb(entity, attr));
+        test(factory().createDropColumnToModel(entity, attr));
+
+        test(factory().createAddRelationshipToDb(entity, rel));
+        test(factory().createAddRelationshipToModel(entity, rel));
+        test(factory().createDropRelationshipToDb(entity, rel));
+        test(factory().createDropRelationshipToModel(entity, rel));
+
+        test(factory().createCreateTableToDb(entity));
+        test(factory().createCreateTableToModel(entity));
+        test(factory().createDropTableToDb(entity));
+        test(factory().createDropTableToModel(entity));
+
+        test(factory().createSetAllowNullToDb(entity, attr));
+        test(factory().createSetAllowNullToModel(entity, attr));
+        test(factory().createSetNotNullToDb(entity, attr));
+        test(factory().createSetNotNullToModel(entity, attr));
+
+        DbAttribute attr2 = dbAttr().build();
+        test(factory().createSetColumnTypeToDb(entity, attr, attr2));
+        test(factory().createSetColumnTypeToModel(entity, attr, attr2));
+
+        test(factory().createSetPrimaryKeyToDb(entity, Collections.singleton(attr), Collections.singleton(attr2), "PK"));
+        test(factory().createSetPrimaryKeyToModel(entity, Collections.singleton(attr), Collections.singleton(attr2), "PK"));
+
+        test(factory().createSetValueForNullToDb(entity, attr, new DefaultValueForNullProvider()));
+    }
+
+    private void test(MergerToken token1) {
+        MergerToken token2 = token1.createReverse(factory()).createReverse(factory());
+
+        Assert.assertEquals(token1.getTokenName(), token2.getTokenName());
+        Assert.assertEquals(token1.getTokenValue(), token2.getTokenValue());
+        Assert.assertEquals(token1.getDirection(), token2.getDirection());
+    }
+
+    private MergerTokenFactory factory() {
+        return new HSQLMergerTokenFactory();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensToModelExecutionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensToModelExecutionTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensToModelExecutionTest.java
new file mode 100644
index 0000000..b9abea1
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensToModelExecutionTest.java
@@ -0,0 +1,80 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dbsync.merge.factory.DefaultMergerTokenFactory;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Test;
+
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dataMap;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbAttr;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbEntity;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @since 4.0.
+ */
+public class TokensToModelExecutionTest {
+
+    @Test
+    public void testCreateAndDropTable() throws Exception {
+        DbEntity entity = dbEntity().build();
+
+        DataMap dataMap = dataMap().build();
+        assertTrue(dataMap.getDbEntityMap().isEmpty());
+        assertTrue(dataMap.getObjEntityMap().isEmpty());
+
+        MergerContext context = MergerContext.builder(dataMap).dataNode(new DataNode()).build();
+        new DefaultMergerTokenFactory().createCreateTableToModel(entity).execute(context);
+
+        assertEquals(1, dataMap.getDbEntityMap().size());
+        assertEquals(1, dataMap.getObjEntities().size());
+        assertEquals(entity, dataMap.getDbEntity(entity.getName()));
+
+        new DefaultMergerTokenFactory().createDropTableToModel(entity).execute(context);
+        assertTrue(dataMap.getDbEntityMap().isEmpty());
+        assertTrue(dataMap.getObjEntityMap().isEmpty());
+    }
+
+    @Test
+    public void testCreateAndDropColumn() throws Exception {
+        DbAttribute attr = dbAttr("attr").build();
+        DbEntity entity = dbEntity().build();
+
+        DataMap dataMap = dataMap().with(entity).build();
+        assertEquals(1, dataMap.getDbEntityMap().size());
+        assertTrue(dataMap.getObjEntityMap().isEmpty());
+
+        MergerContext context = MergerContext.builder(dataMap).dataNode(new DataNode()).build();
+        new DefaultMergerTokenFactory().createAddColumnToModel(entity, attr).execute(context);
+
+        assertEquals(1, dataMap.getDbEntityMap().size());
+        assertEquals(1, entity.getAttributes().size());
+        assertEquals(attr, entity.getAttribute(attr.getName()));
+
+        new DefaultMergerTokenFactory().createDropColumnToModel(entity, attr).execute(context);
+        assertEquals(1, dataMap.getDbEntityMap().size());
+        assertTrue(entity.getAttributes().isEmpty());
+        assertTrue(dataMap.getObjEntityMap().isEmpty());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/ValueForNullIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/ValueForNullIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/ValueForNullIT.java
new file mode 100644
index 0000000..e9910fd
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/ValueForNullIT.java
@@ -0,0 +1,127 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import junit.framework.AssertionFailedError;
+import org.apache.cayenne.DataObject;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.access.jdbc.SQLParameterBinding;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.SelectQuery;
+import org.junit.Test;
+
+import java.sql.Types;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ValueForNullIT extends MergeCase {
+
+	private static final String DEFAULT_VALUE_STRING = "DEFSTRING";
+
+	@Inject
+	private DataContext context;
+
+	@Test
+	public void test() throws Exception {
+		DbEntity dbEntity = map.getDbEntity("PAINTING");
+		assertNotNull(dbEntity);
+		ObjEntity objEntity = map.getObjEntity("Painting");
+		assertNotNull(objEntity);
+
+		// insert some rows before adding "not null" column
+		final int nrows = 10;
+		for (int i = 0; i < nrows; i++) {
+			DataObject o = (DataObject) context.newObject("Painting");
+			o.writeProperty("paintingTitle", "ptitle" + i);
+		}
+		context.commitChanges();
+
+		// create and add new column to model and db
+		DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
+
+		column.setMandatory(false);
+		column.setMaxLength(10);
+		dbEntity.addAttribute(column);
+		assertTrue(dbEntity.getAttributes().contains(column));
+		assertEquals(column, dbEntity.getAttribute(column.getName()));
+		assertTokensAndExecute(1, 0);
+
+		// need obj attr to be able to query
+		ObjAttribute objAttr = new ObjAttribute("newcol2");
+		objAttr.setDbAttributePath(column.getName());
+		objEntity.addAttribute(objAttr);
+
+		// check that is was merged
+		assertTokensAndExecute(0, 0);
+
+		// set not null
+		column.setMandatory(true);
+
+		// merge to db
+		assertTokensAndExecute(2, 0);
+
+		// check that is was merged
+		assertTokensAndExecute(0, 0);
+
+		// check values for null
+		Expression qual = ExpressionFactory.matchExp(objAttr.getName(), DEFAULT_VALUE_STRING);
+		SelectQuery query = new SelectQuery("Painting", qual);
+		List<Persistent> rows = context.performQuery(query);
+		assertEquals(nrows, rows.size());
+
+		// clean up
+		dbEntity.removeAttribute(column.getName());
+		assertTokensAndExecute(1, 0);
+		assertTokensAndExecute(0, 0);
+	}
+
+	@Override
+	protected DbMerger createMerger(final ValueForNullProvider valueForNullProvider) {
+		return super.createMerger(new DefaultValueForNullProvider() {
+
+			@Override
+			protected SQLParameterBinding get(DbEntity entity, DbAttribute column) {
+				int type = column.getType();
+				switch (type) {
+				case Types.VARCHAR:
+					return new SQLParameterBinding(DEFAULT_VALUE_STRING, type, -1);
+				default:
+					throw new AssertionFailedError("should not get here");
+				}
+			}
+
+			@Override
+			public boolean hasValueFor(DbEntity entity, DbAttribute column) {
+				return true;
+			}
+
+		});
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/Builder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/Builder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/Builder.java
new file mode 100644
index 0000000..baf8240
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/Builder.java
@@ -0,0 +1,38 @@
+/*****************************************************************
+ *   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.dbsync.merge.builders;
+
+/**
+ * Base interface for all domain builders
+ *
+ * @since 4.0.
+ */
+public interface Builder<T> {
+
+    /**
+     * Build valid object. If some required data omitted it will be filled with random data.
+     * */
+    T build();
+
+    /**
+     * Build valid object and add some optional fields randomly.
+     * */
+    T random();
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DataMapBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DataMapBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DataMapBuilder.java
new file mode 100644
index 0000000..a7aa11c
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DataMapBuilder.java
@@ -0,0 +1,128 @@
+/*****************************************************************
+ *   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.dbsync.merge.builders;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.ObjEntity;
+
+import java.util.Collections;
+
+/**
+ * @since 4.0.
+ */
+public class DataMapBuilder extends DefaultBuilder<DataMap> {
+
+    public DataMapBuilder() {
+        this(new DataMap());
+    }
+
+    public DataMapBuilder(DataMap dataMap) {
+        super(dataMap);
+    }
+
+    public DataMapBuilder with(DbEntity ... entities) {
+        for (DbEntity entity : entities) {
+            obj.addDbEntity(entity);
+        }
+
+        return this;
+    }
+
+    public DataMapBuilder with(DbEntityBuilder ... entities) {
+        for (DbEntityBuilder entity : entities) {
+            obj.addDbEntity(entity.build());
+        }
+
+        return this;
+    }
+
+    public DataMapBuilder withDbEntities(int count) {
+        for (int i = 0; i < count; i++) {
+            obj.addDbEntity(ObjectMother.dbEntity().random());
+        }
+
+        return this;
+    }
+
+    public DataMapBuilder with(ObjEntity... entities) {
+        for (ObjEntity entity : entities) {
+            obj.addObjEntity(entity);
+        }
+
+        return this;
+    }
+
+    public DataMapBuilder with(ObjEntityBuilder ... entities) {
+        for (ObjEntityBuilder entity : entities) {
+            obj.addObjEntity(entity.build());
+        }
+
+        return this;
+    }
+
+    public DataMapBuilder withObjEntities(int count) {
+        for (int i = 0; i < count; i++) {
+            obj.addObjEntity(ObjectMother.objEntity().random());
+        }
+
+        return this;
+    }
+
+    public DataMapBuilder join(String from, String to) {
+        return join(null, from, to);
+    }
+
+    public DataMapBuilder join(String name, String from, String to) {
+        String[] fromSplit = from.split("\\.");
+        DbEntity fromEntity = obj.getDbEntity(fromSplit[0]);
+        if (fromEntity == null) {
+            throw new IllegalArgumentException("Entity '" + fromSplit[0] + "' is undefined");
+        }
+
+        String[] toSplit = to.split("\\.");
+
+        fromEntity.addRelationship(new DbRelationshipBuilder(name)
+                .from(fromEntity, fromSplit[1])
+                .to(toSplit[0], toSplit[1])
+
+                .build());
+
+        return this;
+    }
+
+    public DataMap build() {
+        if (obj.getNamespace() == null) {
+            obj.setNamespace(new EntityResolver(Collections.singleton(obj)));
+        }
+
+        return obj;
+    }
+
+    @Override
+    public DataMap random() {
+        if (dataFactory.chance(90)) {
+            withDbEntities(dataFactory.getNumberUpTo(10));
+        }
+
+
+        return build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbAttributeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbAttributeBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbAttributeBuilder.java
new file mode 100644
index 0000000..2d600f3
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbAttributeBuilder.java
@@ -0,0 +1,115 @@
+/*****************************************************************
+ *   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.dbsync.merge.builders;
+
+import org.apache.cayenne.datafactory.DictionaryValueProvider;
+import org.apache.cayenne.datafactory.ValueProvider;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.map.DbAttribute;
+
+import static org.apache.commons.lang.StringUtils.isEmpty;
+
+/**
+ * @since 4.0.
+ */
+public class DbAttributeBuilder extends DefaultBuilder<DbAttribute> {
+
+    private static final ValueProvider<String> TYPES_RANDOM = new DictionaryValueProvider<String>(ValueProvider.RANDOM) {
+        @Override
+        protected String[] values() {
+            return TypesMapping.getDatabaseTypes();
+        }
+    };
+
+    public DbAttributeBuilder() {
+        super(new DbAttribute());
+    }
+
+    public DbAttributeBuilder name() {
+        return name(getRandomJavaName());
+    }
+
+    public DbAttributeBuilder name(String name) {
+        obj.setName(name);
+
+        return this;
+    }
+
+    public DbAttributeBuilder type() {
+        return type(TYPES_RANDOM.randomValue());
+    }
+
+    public DbAttributeBuilder type(String item) {
+        obj.setType(TypesMapping.getSqlTypeByName(item));
+
+        return this;
+    }
+
+    public DbAttributeBuilder typeInt() {
+        return type(TypesMapping.SQL_INTEGER);
+    }
+
+    public DbAttributeBuilder typeBigInt() {
+        return type(TypesMapping.SQL_BIGINT);
+    }
+
+    public DbAttributeBuilder typeVarchar(int length) {
+        type(TypesMapping.SQL_VARCHAR);
+        length(length);
+
+        return this;
+    }
+
+    private DbAttributeBuilder length(int length) {
+        obj.setMaxLength(length);
+
+        return this;
+    }
+
+    public DbAttributeBuilder primaryKey() {
+        obj.setPrimaryKey(true);
+
+        return this;
+    }
+
+    public DbAttributeBuilder mandatory() {
+        obj.setMandatory(true);
+
+        return this;
+    }
+
+    @Override
+    public DbAttribute build() {
+        if (isEmpty(obj.getName())) {
+            name();
+        }
+
+        if (obj.getType() == TypesMapping.NOT_DEFINED) {
+            type();
+        }
+
+        return obj;
+    }
+
+    @Override
+    public DbAttribute random() {
+        return build();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbEntityBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbEntityBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbEntityBuilder.java
new file mode 100644
index 0000000..03f0738
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbEntityBuilder.java
@@ -0,0 +1,90 @@
+/*****************************************************************
+ *   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.dbsync.merge.builders;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 4.0.
+ */
+public class DbEntityBuilder extends DefaultBuilder<DbEntity> {
+
+    public DbEntityBuilder() {
+        super(new DbEntity());
+    }
+
+    public DbEntityBuilder name() {
+        return name(getRandomJavaName());
+    }
+
+    public DbEntityBuilder name(String name) {
+        obj.setName(name);
+
+        return this;
+    }
+
+    public DbEntityBuilder attributes(DbAttribute ... attributes) {
+        for (DbAttribute attribute : attributes) {
+            obj.addAttribute(attribute);
+        }
+
+        return this;
+    }
+
+    public DbEntityBuilder attributes(DbAttributeBuilder ... attributes) {
+        for (DbAttributeBuilder attribute : attributes) {
+            obj.addAttribute(attribute.build());
+        }
+
+        return this;
+    }
+
+    public DbEntityBuilder attributes(int numberUpTo) {
+        for (int i = 0; i < numberUpTo; i++) {
+            try {
+                obj.addAttribute(new DbAttributeBuilder().random());
+            } catch (IllegalArgumentException e) {
+                i--; // try again
+            }
+        }
+
+        return this;
+    }
+
+
+    @Override
+    public DbEntity build() {
+        if (obj.getName() == null) {
+            obj.setName(StringUtils.capitalize(getRandomJavaName()));
+        }
+
+        return obj;
+    }
+
+    @Override
+    public DbEntity random() {
+        if (dataFactory.chance(99)) {
+            attributes(dataFactory.getNumberUpTo(20));
+        }
+
+        return build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbRelationshipBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbRelationshipBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbRelationshipBuilder.java
new file mode 100644
index 0000000..ae87549
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbRelationshipBuilder.java
@@ -0,0 +1,85 @@
+/*****************************************************************
+ *   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.dbsync.merge.builders;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+
+/**
+ * @since 4.0.
+ */
+public class DbRelationshipBuilder extends DefaultBuilder<DbRelationship> {
+
+    private String[] from;
+    private String[] to;
+
+    public DbRelationshipBuilder() {
+        super(new DbRelationship());
+    }
+
+    public DbRelationshipBuilder(String name) {
+        super(new DbRelationship(name));
+    }
+
+    public DbRelationshipBuilder(DbRelationship obj) {
+        super(obj);
+    }
+
+    public DbRelationshipBuilder name() {
+        return name(getRandomJavaName());
+    }
+
+    public DbRelationshipBuilder name(String name) {
+        obj.setName(name);
+
+        return this;
+    }
+
+    public DbRelationshipBuilder from(DbEntity entity, String ... columns) {
+        obj.setSourceEntity(entity);
+        this.from = columns;
+
+        return this;
+    }
+
+    public DbRelationshipBuilder to(String entityName, String ... columns) {
+        obj.setTargetEntityName(entityName);
+        this.to = columns;
+
+        return this;
+    }
+
+    @Override
+    public DbRelationship build() {
+        if (obj.getName() == null) {
+            name();
+        }
+
+        if (from.length != to.length) {
+            throw new IllegalStateException("from and to columns name size mismatch");
+        }
+
+        for (int i = 0; i < from.length; i++) {
+            obj.addJoin(new DbJoin(obj, from[i], to[i]));
+        }
+
+        return obj;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DefaultBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DefaultBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DefaultBuilder.java
new file mode 100644
index 0000000..559347b
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DefaultBuilder.java
@@ -0,0 +1,57 @@
+/*****************************************************************
+ *   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.dbsync.merge.builders;
+
+import org.apache.cayenne.datafactory.DataFactory;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 4.0.
+ */
+public abstract class DefaultBuilder<T> implements Builder<T> {
+
+    protected final DataFactory dataFactory;
+    protected final T obj;
+
+
+    protected DefaultBuilder(T obj) {
+        this.dataFactory = new DataFactory();
+        this.obj = obj;
+    }
+
+    public String getRandomJavaName() {
+        int count = dataFactory.getNumberBetween(1, 5);
+        StringBuilder res = new StringBuilder();
+        for (int i = 0; i < count; i++) {
+            res.append(StringUtils.capitalize(dataFactory.getRandomWord()));
+        }
+
+        return StringUtils.uncapitalize(res.toString());
+    }
+
+    @Override
+    public T build() {
+        return obj;
+    }
+
+    @Override
+    public T random() {
+        return build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjAttributeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjAttributeBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjAttributeBuilder.java
new file mode 100644
index 0000000..a183c34
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjAttributeBuilder.java
@@ -0,0 +1,67 @@
+/*****************************************************************
+ *   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.dbsync.merge.builders;
+
+import org.apache.cayenne.map.ObjAttribute;
+
+/**
+ * @since 4.0.
+ */
+public class ObjAttributeBuilder extends DefaultBuilder<ObjAttribute> {
+
+    public ObjAttributeBuilder() {
+        super(new ObjAttribute());
+    }
+
+    public ObjAttributeBuilder name() {
+        return name(getRandomJavaName());
+    }
+
+    public ObjAttributeBuilder name(String name) {
+        obj.setName(name);
+
+        return this;
+    }
+
+    public ObjAttributeBuilder type(Class type) {
+        obj.setType(type.getCanonicalName());
+
+        return this;
+    }
+
+    public ObjAttributeBuilder dbPath(String path) {
+        obj.setDbAttributePath(path);
+
+        return this;
+    }
+
+    @Override
+    public ObjAttribute build() {
+        if (obj.getName() == null) {
+            name();
+        }
+
+        return obj;
+    }
+
+    @Override
+    public ObjAttribute random() {
+        return build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjEntityBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjEntityBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjEntityBuilder.java
new file mode 100644
index 0000000..f2f701c
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjEntityBuilder.java
@@ -0,0 +1,98 @@
+/*****************************************************************
+ *   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.dbsync.merge.builders;
+
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 4.0.
+ */
+public class ObjEntityBuilder extends DefaultBuilder<ObjEntity> {
+
+    public ObjEntityBuilder() {
+        super(new ObjEntity());
+    }
+
+    public ObjEntityBuilder name() {
+        return name(getRandomJavaName());
+    }
+
+    public ObjEntityBuilder name(String name) {
+        obj.setName(name);
+
+        return this;
+    }
+
+    public ObjEntityBuilder attributes(ObjAttribute... attributes) {
+        for (ObjAttribute attribute : attributes) {
+            obj.addAttribute(attribute);
+        }
+
+        return this;
+    }
+
+    public ObjEntityBuilder attributes(ObjAttributeBuilder ... attributes) {
+        for (ObjAttributeBuilder attribute : attributes) {
+            obj.addAttribute(attribute.build());
+        }
+
+        return this;
+    }
+
+    public ObjEntityBuilder attributes(int numberUpTo) {
+        for (int i = 0; i < numberUpTo; i++) {
+            obj.addAttribute(new ObjAttributeBuilder().random());
+        }
+
+        return this;
+    }
+
+
+    @Override
+    public ObjEntity build() {
+        if (obj.getName() == null) {
+            obj.setName(StringUtils.capitalize(getRandomJavaName()));
+        }
+
+        return obj;
+    }
+
+    @Override
+    public ObjEntity random() {
+        if (dataFactory.chance(99)) {
+            attributes(dataFactory.getNumberUpTo(20));
+        }
+
+        return build();
+    }
+
+    public ObjEntityBuilder clazz(String s) {
+        obj.setClassName(s);
+
+        return this;
+    }
+
+    public ObjEntityBuilder dbEntity(String table) {
+        obj.setDbEntityName(table);
+
+        return this;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjectMother.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjectMother.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjectMother.java
new file mode 100644
index 0000000..0097fe1
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjectMother.java
@@ -0,0 +1,70 @@
+/*****************************************************************
+ *   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.dbsync.merge.builders;
+
+import org.apache.cayenne.map.DataMap;
+
+/**
+ * Factory for test data see pattern definition:
+ * http://martinfowler.com/bliki/ObjectMother.html
+ *
+ * @since 4.0.
+ */
+public class ObjectMother {
+
+    public static DataMapBuilder dataMap() {
+        return new DataMapBuilder();
+    }
+
+    public static DataMapBuilder dataMap(DataMap dataMap) {
+        return new DataMapBuilder(dataMap);
+    }
+
+    public static DbEntityBuilder dbEntity() {
+        return new DbEntityBuilder();
+    }
+
+    public static DbEntityBuilder dbEntity(String name) {
+        return new DbEntityBuilder().name(name);
+    }
+
+    public static ObjEntityBuilder objEntity() {
+        return new ObjEntityBuilder();
+    }
+
+    public static ObjEntityBuilder objEntity(String packageName, String className, String table) {
+        return new ObjEntityBuilder()
+                .name(className)
+                .clazz(packageName + "." + className)
+                .dbEntity(table);
+    }
+
+    public static ObjAttributeBuilder objAttr(String name) {
+        return new ObjAttributeBuilder().name(name);
+    }
+
+    public static DbAttributeBuilder dbAttr(String name) {
+        return dbAttr().name(name);
+    }
+
+    public static DbAttributeBuilder dbAttr() {
+        return new DbAttributeBuilder();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderIT.java
new file mode 100644
index 0000000..a902754
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderIT.java
@@ -0,0 +1,430 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.*;
+import org.apache.cayenne.unit.UnitDbAdapter;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Types;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class DbLoaderIT extends ServerCase {
+
+    public static final DbLoaderConfiguration CONFIG = new DbLoaderConfiguration();
+    @Inject
+    private ServerRuntime runtime;
+
+    @Inject
+    private DbAdapter adapter;
+
+    @Inject
+    private ServerCaseDataSourceFactory dataSourceFactory;
+
+    @Inject
+    private UnitDbAdapter accessStackAdapter;
+
+    private DbLoader loader;
+
+    @Before
+    public void setUp() throws Exception {
+        loader = new DbLoader(dataSourceFactory.getSharedDataSource().getConnection(), adapter, null);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        loader.getConnection().close();
+    }
+
+    @Test
+    public void testGetTableTypes() throws Exception {
+
+        List<?> tableTypes = loader.getTableTypes();
+
+        assertNotNull(tableTypes);
+
+        String tableLabel = adapter.tableTypeForTable();
+        if (tableLabel != null) {
+            assertTrue("Missing type for table '" + tableLabel + "' - " + tableTypes, tableTypes.contains(tableLabel));
+        }
+
+        String viewLabel = adapter.tableTypeForView();
+        if (viewLabel != null) {
+            assertTrue("Missing type for view '" + viewLabel + "' - " + tableTypes, tableTypes.contains(viewLabel));
+        }
+    }
+
+    @Test
+    public void testGetTables() throws Exception {
+
+        String tableLabel = adapter.tableTypeForTable();
+
+        List<DetectedDbEntity> tables = loader.createTableLoader(null, null, TableFilter.everything())
+                .getDbEntities(TableFilter.everything(), new String[]{tableLabel});
+
+        assertNotNull(tables);
+
+        boolean foundArtist = false;
+
+        for (DetectedDbEntity table : tables) {
+            if ("ARTIST".equalsIgnoreCase(table.getName())) {
+                foundArtist = true;
+                break;
+            }
+        }
+
+        assertTrue("'ARTIST' is missing from the table list: " + tables, foundArtist);
+    }
+
+    @Test
+    public void testGetTablesWithWrongCatalog() throws Exception {
+
+        DbLoaderConfiguration config = new DbLoaderConfiguration();
+        config.setFiltersConfig(
+                FiltersConfig.create("WRONG", null, TableFilter.everything(), PatternFilter.INCLUDE_NOTHING));
+        List<DetectedDbEntity> tables = loader
+                .createTableLoader("WRONG", null, TableFilter.everything())
+                    .getDbEntities(TableFilter.everything(), new String[]{adapter.tableTypeForTable()});
+
+        assertNotNull(tables);
+        assertTrue(tables.isEmpty());
+    }
+
+    @Test
+    public void testGetTablesWithWrongSchema() throws Exception {
+
+        DbLoaderConfiguration config = new DbLoaderConfiguration();
+        config.setFiltersConfig(
+                FiltersConfig.create(null, "WRONG", TableFilter.everything(), PatternFilter.INCLUDE_NOTHING));
+        List<DetectedDbEntity> tables = loader
+                .createTableLoader(null, "WRONG", TableFilter.everything())
+                .getDbEntities(TableFilter.everything(), new String[]{adapter.tableTypeForTable()});
+
+        assertNotNull(tables);
+        assertTrue(tables.isEmpty());
+    }
+
+    @Test
+    public void testLoadWithMeaningfulPK() throws Exception {
+
+        DataMap map = new DataMap();
+        String[] tableLabel = { adapter.tableTypeForTable() };
+
+        loader.setCreatingMeaningfulPK(true);
+
+        List<DbEntity> entities = loader
+                .createTableLoader(null, null, TableFilter.everything())
+                .loadDbEntities(map, CONFIG, tableLabel);
+
+        loader.loadObjEntities(map, CONFIG, entities);
+
+        ObjEntity artist = map.getObjEntity("Artist");
+        assertNotNull(artist);
+
+        ObjAttribute id = artist.getAttribute("artistId");
+        assertNotNull(id);
+    }
+
+    /**
+     * DataMap loading is in one big test method, since breaking it in
+     * individual tests would require multiple reads of metatdata which is
+     * extremely slow on some RDBMS (Sybase).
+     */
+    @Test
+    public void testLoad() throws Exception {
+
+        boolean supportsUnique = runtime.getDataDomain().getDataNodes().iterator().next().getAdapter()
+                .supportsUniqueConstraints();
+        boolean supportsLobs = accessStackAdapter.supportsLobs();
+        boolean supportsFK = accessStackAdapter.supportsFKConstraints();
+
+        DataMap map = new DataMap();
+        map.setDefaultPackage("foo.x");
+
+        String tableLabel = adapter.tableTypeForTable();
+
+        // *** TESTING THIS ***
+        List<DbEntity> entities = loader
+                .createTableLoader(null, null, TableFilter.everything())
+                .loadDbEntities(map, CONFIG, new String[]{adapter.tableTypeForTable()});
+
+
+        assertDbEntities(map);
+
+        if (supportsLobs) {
+            assertLobDbEntities(map);
+        }
+
+        // *** TESTING THIS ***
+        loader.loadDbRelationships(CONFIG, null, null, entities);
+
+        if (supportsFK) {
+            Collection<DbRelationship> rels = getDbEntity(map, "ARTIST").getRelationships();
+            assertNotNull(rels);
+            assertTrue(!rels.isEmpty());
+
+            // test one-to-one
+            rels = getDbEntity(map, "PAINTING").getRelationships();
+            assertNotNull(rels);
+
+            // find relationship to PAINTING_INFO
+            DbRelationship oneToOne = null;
+            for (DbRelationship rel : rels) {
+                if ("PAINTING_INFO".equalsIgnoreCase(rel.getTargetEntityName())) {
+                    oneToOne = rel;
+                    break;
+                }
+            }
+
+            assertNotNull("No relationship to PAINTING_INFO", oneToOne);
+            assertFalse("Relationship to PAINTING_INFO must be to-one", oneToOne.isToMany());
+            assertTrue("Relationship to PAINTING_INFO must be to-one", oneToOne.isToDependentPK());
+
+            // test UNIQUE only if FK is supported...
+            if (supportsUnique) {
+                assertUniqueConstraintsInRelationships(map);
+            }
+        }
+
+        // *** TESTING THIS ***
+        loader.setCreatingMeaningfulPK(false);
+        loader.loadObjEntities(map, CONFIG, entities);
+
+        assertObjEntities(map);
+
+        // now when the map is loaded, test
+        // various things
+        // selectively check how different types were processed
+        if (accessStackAdapter.supportsColumnTypeReengineering()) {
+            checkTypes(map);
+        }
+    }
+
+    private void assertUniqueConstraintsInRelationships(DataMap map) {
+        // unfortunately JDBC metadata doesn't provide info for UNIQUE
+        // constraints....
+        // cant reengineer them...
+
+        // find rel to TO_ONEFK1
+        /*
+         * Iterator it = getDbEntity(map,
+         * "TO_ONEFK2").getRelationships().iterator(); DbRelationship rel =
+         * (DbRelationship) it.next(); assertEquals("TO_ONEFK1",
+         * rel.getTargetEntityName());
+         * assertFalse("UNIQUE constraint was ignored...", rel.isToMany());
+         */
+    }
+
+    private void assertDbEntities(DataMap map) {
+        DbEntity dae = getDbEntity(map, "ARTIST");
+        assertNotNull("Null 'ARTIST' entity, other DbEntities: " + map.getDbEntityMap(), dae);
+        assertEquals("ARTIST", dae.getName().toUpperCase());
+
+        DbAttribute a = getDbAttribute(dae, "ARTIST_ID");
+        assertNotNull(a);
+        assertTrue(a.isPrimaryKey());
+        assertFalse(a.isGenerated());
+
+        if (adapter.supportsGeneratedKeys()) {
+            DbEntity bag = getDbEntity(map, "GENERATED_COLUMN_TEST");
+            DbAttribute id = getDbAttribute(bag, "GENERATED_COLUMN");
+            assertTrue(id.isPrimaryKey());
+            assertTrue(id.isGenerated());
+        }
+    }
+
+    private void assertObjEntities(DataMap map) {
+
+        boolean supportsLobs = accessStackAdapter.supportsLobs();
+        boolean supportsFK = accessStackAdapter.supportsFKConstraints();
+
+        ObjEntity ae = map.getObjEntity("Artist");
+        assertNotNull(ae);
+        assertEquals("Artist", ae.getName());
+
+        // assert primary key is not an attribute
+        assertNull(ae.getAttribute("artistId"));
+
+        if (supportsLobs) {
+            assertLobObjEntities(map);
+        }
+
+        if (supportsFK) {
+            Collection<?> rels1 = ae.getRelationships();
+            assertNotNull(rels1);
+            assertTrue(rels1.size() > 0);
+        }
+
+        assertEquals("foo.x.Artist", ae.getClassName());
+    }
+
+    private void assertLobDbEntities(DataMap map) {
+        DbEntity blobEnt = getDbEntity(map, "BLOB_TEST");
+        assertNotNull(blobEnt);
+        DbAttribute blobAttr = getDbAttribute(blobEnt, "BLOB_COL");
+        assertNotNull(blobAttr);
+        assertTrue(msgForTypeMismatch(Types.BLOB, blobAttr), Types.BLOB == blobAttr.getType()
+                || Types.LONGVARBINARY == blobAttr.getType());
+
+        DbEntity clobEnt = getDbEntity(map, "CLOB_TEST");
+        assertNotNull(clobEnt);
+        DbAttribute clobAttr = getDbAttribute(clobEnt, "CLOB_COL");
+        assertNotNull(clobAttr);
+        assertTrue(msgForTypeMismatch(Types.CLOB, clobAttr), Types.CLOB == clobAttr.getType()
+                || Types.LONGVARCHAR == clobAttr.getType());
+
+/*
+        DbEntity nclobEnt = getDbEntity(map, "NCLOB_TEST");
+        assertNotNull(nclobEnt);
+        DbAttribute nclobAttr = getDbAttribute(nclobEnt, "NCLOB_COL");
+        assertNotNull(nclobAttr);
+        assertTrue(msgForTypeMismatch(Types.NCLOB, nclobAttr), Types.NCLOB == nclobAttr.getType()
+                || Types.LONGVARCHAR == nclobAttr.getType());
+*/
+    }
+
+    private void assertLobObjEntities(DataMap map) {
+        ObjEntity blobEnt = map.getObjEntity("BlobTest");
+        assertNotNull(blobEnt);
+        // BLOBs should be mapped as byte[]
+        ObjAttribute blobAttr = blobEnt.getAttribute("blobCol");
+        assertNotNull("BlobTest.blobCol failed to doLoad", blobAttr);
+        assertEquals("byte[]", blobAttr.getType());
+
+
+        ObjEntity clobEnt = map.getObjEntity("ClobTest");
+        assertNotNull(clobEnt);
+        // CLOBs should be mapped as Strings by default
+        ObjAttribute clobAttr = clobEnt.getAttribute("clobCol");
+        assertNotNull(clobAttr);
+        assertEquals(String.class.getName(), clobAttr.getType());
+
+
+        ObjEntity nclobEnt = map.getObjEntity("NclobTest");
+        assertNotNull(nclobEnt);
+        // CLOBs should be mapped as Strings by default
+        ObjAttribute nclobAttr = nclobEnt.getAttribute("nclobCol");
+        assertNotNull(nclobAttr);
+        assertEquals(String.class.getName(), nclobAttr.getType());
+    }
+
+    private DbEntity getDbEntity(DataMap map, String name) {
+        DbEntity de = map.getDbEntity(name);
+        // sometimes table names get converted to lowercase
+        if (de == null) {
+            de = map.getDbEntity(name.toLowerCase());
+        }
+
+        return de;
+    }
+
+    private DbAttribute getDbAttribute(DbEntity ent, String name) {
+        DbAttribute da = ent.getAttribute(name);
+        // sometimes table names get converted to lowercase
+        if (da == null) {
+            da = ent.getAttribute(name.toLowerCase());
+        }
+
+        return da;
+    }
+
+    private DataMap originalMap() {
+        return runtime.getDataDomain().getDataNodes().iterator().next().getDataMaps().iterator().next();
+    }
+
+    /**
+     * Selectively check how different types were processed.
+     */
+    public void checkTypes(DataMap map) {
+        DbEntity dbe = getDbEntity(map, "PAINTING");
+        DbEntity floatTest = getDbEntity(map, "FLOAT_TEST");
+        DbEntity smallintTest = getDbEntity(map, "SMALLINT_TEST");
+        DbAttribute integerAttr = getDbAttribute(dbe, "PAINTING_ID");
+        DbAttribute decimalAttr = getDbAttribute(dbe, "ESTIMATED_PRICE");
+        DbAttribute varcharAttr = getDbAttribute(dbe, "PAINTING_TITLE");
+        DbAttribute floatAttr = getDbAttribute(floatTest, "FLOAT_COL");
+        DbAttribute smallintAttr = getDbAttribute(smallintTest, "SMALLINT_COL");
+
+        // check decimal
+        assertTrue(msgForTypeMismatch(Types.DECIMAL, decimalAttr), Types.DECIMAL == decimalAttr.getType()
+                || Types.NUMERIC == decimalAttr.getType());
+        assertEquals(2, decimalAttr.getScale());
+
+        // check varchar
+        assertEquals(msgForTypeMismatch(Types.VARCHAR, varcharAttr), Types.VARCHAR, varcharAttr.getType());
+        assertEquals(255, varcharAttr.getMaxLength());
+        // check integer
+        assertEquals(msgForTypeMismatch(Types.INTEGER, integerAttr), Types.INTEGER, integerAttr.getType());
+        // check float
+        assertTrue(msgForTypeMismatch(Types.FLOAT, floatAttr), Types.FLOAT == floatAttr.getType()
+                || Types.DOUBLE == floatAttr.getType() || Types.REAL == floatAttr.getType());
+
+        // check smallint
+        assertTrue(msgForTypeMismatch(Types.SMALLINT, smallintAttr), Types.SMALLINT == smallintAttr.getType()
+                || Types.INTEGER == smallintAttr.getType());
+    }
+
+    public void checkAllDBEntities(DataMap map) {
+
+        for (DbEntity origEnt : originalMap().getDbEntities()) {
+            DbEntity newEnt = map.getDbEntity(origEnt.getName());
+            for (DbAttribute origAttr : origEnt.getAttributes()) {
+                DbAttribute newAttr = newEnt.getAttribute(origAttr.getName());
+                assertNotNull("No matching DbAttribute for '" + origAttr.getName(), newAttr);
+                assertEquals(msgForTypeMismatch(origAttr, newAttr), origAttr.getType(), newAttr.getType());
+                // length and precision doesn't have to be the same
+                // it must be greater or equal
+                assertTrue(origAttr.getMaxLength() <= newAttr.getMaxLength());
+                assertTrue(origAttr.getScale() <= newAttr.getScale());
+            }
+        }
+    }
+
+    private static String msgForTypeMismatch(DbAttribute origAttr, DbAttribute newAttr) {
+        return msgForTypeMismatch(origAttr.getType(), 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 static String attrMismatch(String attrName, String msg) {
+        return "[Error loading attribute '" + attrName + "': " + msg + "]";
+    }
+}


[03/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/dbimport/FiltersConfigBuilderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/dbimport/FiltersConfigBuilderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/dbimport/FiltersConfigBuilderTest.java
deleted file mode 100644
index 9a94618..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/dbimport/FiltersConfigBuilderTest.java
+++ /dev/null
@@ -1,382 +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.dbimport;
-
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-public class FiltersConfigBuilderTest {
-
-    @Test
-    public void testCompact_01() {
-        ReverseEngineering engineering = new ReverseEngineering();
-        engineering.addIncludeTable(new IncludeTable("table1"));
-        engineering.addIncludeTable(new IncludeTable("table2"));
-        engineering.addIncludeTable(new IncludeTable("table3"));
-
-        engineering.addIncludeColumn(new IncludeColumn("includeColumn"));
-
-        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
-        builder.compact();
-        assertEquals(
-                "ReverseEngineering: \n" +
-                "  Catalog: null\n" +
-                "    Schema: null\n" +
-                "      IncludeTable: table1\n" +
-                "        IncludeColumn: includeColumn\n" +
-                "      IncludeTable: table2\n" +
-                "        IncludeColumn: includeColumn\n" +
-                "      IncludeTable: table3\n" +
-                "        IncludeColumn: includeColumn\n", engineering.toString());
-    }
-
-    @Test
-    public void testCompact_02() {
-        ReverseEngineering engineering = new ReverseEngineering();
-        engineering.addCatalog(new Catalog("catalogName"));
-        engineering.addSchema(new Schema("schemaName01"));
-        engineering.addSchema(new Schema("schemaName02"));
-
-        engineering.addIncludeTable(new IncludeTable("table1"));
-        engineering.addExcludeTable(new ExcludeTable("table2"));
-
-        engineering.addIncludeColumn(new IncludeColumn("includeColumn"));
-
-        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
-        builder.compact();
-        assertEquals(
-                "ReverseEngineering: \n" +
-                "  Catalog: catalogName\n" +
-                "    Schema: schemaName01\n" +
-                "      IncludeTable: table1\n" +
-                "        IncludeColumn: includeColumn\n" +
-                "      ExcludeTable: table2\n" +
-                "    Schema: schemaName02\n" +
-                "      IncludeTable: table1\n" +
-                "        IncludeColumn: includeColumn\n" +
-                "      ExcludeTable: table2\n", engineering.toString());
-    }
-
-    @Test
-    public void testCompact_03() {
-        ReverseEngineering engineering = new ReverseEngineering();
-        engineering.addCatalog(new Catalog("APP1"));
-        engineering.addCatalog(new Catalog("APP2"));
-
-        engineering.addExcludeTable(new ExcludeTable("SYS_.*"));
-        engineering.addExcludeColumn(new ExcludeColumn("calculated_.*"));
-
-        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
-        builder.compact();
-        assertEquals(
-                "ReverseEngineering: \n" +
-                "  Catalog: APP1\n" +
-                "    Schema: null\n" +
-                "      IncludeTable: null\n" +
-                "        ExcludeColumn: calculated_.*\n" +
-                "      ExcludeTable: SYS_.*\n" +
-                "  Catalog: APP2\n" +
-                "    Schema: null\n" +
-                "      IncludeTable: null\n" +
-                "        ExcludeColumn: calculated_.*\n" +
-                "      ExcludeTable: SYS_.*\n", engineering.toString());
-    }
-
-    @Test
-    public void testCompact_04() {
-        ReverseEngineering engineering = new ReverseEngineering();
-        engineering.addSchema(new Schema("s"));
-
-        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
-        builder.compact();
-        assertEquals(
-                "ReverseEngineering: \n" +
-                "  Catalog: null\n" +
-                "    Schema: s\n" +
-                "      IncludeTable: null\n", engineering.toString());
-    }
-
-    @Test
-    public void testCompact_full() {
-        ReverseEngineering engineering = new ReverseEngineering();
-        Catalog cat01 = new Catalog("cat_01");
-
-        Schema sch01 = new Schema("sch_01");
-
-        sch01.addIncludeTable(includeTable("t1", "c11", "c12"));
-        sch01.addExcludeTable(new ExcludeTable("t2"));
-        sch01.addIncludeProcedure(new IncludeProcedure("p1"));
-        sch01.addExcludeProcedure(new ExcludeProcedure("p2"));
-        sch01.addIncludeColumn(new IncludeColumn("c_x1"));
-        sch01.addExcludeColumn(new ExcludeColumn("c_x2"));
-
-        cat01.addSchema(sch01);
-
-        cat01.addIncludeTable(includeTable("t3", "c31", "c32"));
-        cat01.addExcludeTable(new ExcludeTable("t4"));
-        cat01.addIncludeProcedure(new IncludeProcedure("p3"));
-        cat01.addExcludeProcedure(new ExcludeProcedure("p4"));
-        cat01.addIncludeColumn(new IncludeColumn("c_xx1"));
-        cat01.addExcludeColumn(new ExcludeColumn("c_xx2"));
-
-        engineering.addCatalog(cat01);
-
-        Schema sch02 = new Schema("sch_02");
-
-        sch02.addIncludeTable(includeTable("t5", "c51", "c52"));
-        sch02.addExcludeTable(new ExcludeTable("t6"));
-        sch02.addIncludeProcedure(new IncludeProcedure("p5"));
-        sch02.addExcludeProcedure(new ExcludeProcedure("p6"));
-        sch02.addIncludeColumn(new IncludeColumn("c2_x1"));
-        sch02.addExcludeColumn(new ExcludeColumn("c2_x2"));
-
-        engineering.addSchema(sch02);
-
-        engineering.addIncludeTable(includeTable("t7", "c71", "c72"));
-        engineering.addExcludeTable(new ExcludeTable("t8"));
-        engineering.addIncludeProcedure(new IncludeProcedure("p7"));
-        engineering.addExcludeProcedure(new ExcludeProcedure("p8"));
-        engineering.addIncludeColumn(new IncludeColumn("c_xxx1"));
-        engineering.addExcludeColumn(new ExcludeColumn("c_xxx2"));
-
-        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
-        assertEquals("Original ReverseEngineering should be",
-                "ReverseEngineering: \n" +
-                "  Catalog: cat_01\n" +
-                "    Schema: sch_01\n" +
-                "      IncludeTable: t1\n" +
-                "        IncludeColumn: c11\n" +
-                "        ExcludeColumn: c12\n" +
-                "      ExcludeTable: t2\n" +
-                "      IncludeColumn: c_x1\n" +
-                "      ExcludeColumn: c_x2\n" +
-                "      IncludeProcedure: p1\n" +
-                "      ExcludeProcedure: p2\n" +
-                "    IncludeTable: t3\n" +
-                "      IncludeColumn: c31\n" +
-                "      ExcludeColumn: c32\n" +
-                "    ExcludeTable: t4\n" +
-                "    IncludeColumn: c_xx1\n" +
-                "    ExcludeColumn: c_xx2\n" +
-                "    IncludeProcedure: p3\n" +
-                "    ExcludeProcedure: p4\n" +
-                "  Schema: sch_02\n" +
-                "    IncludeTable: t5\n" +
-                "      IncludeColumn: c51\n" +
-                "      ExcludeColumn: c52\n" +
-                "    ExcludeTable: t6\n" +
-                "    IncludeColumn: c2_x1\n" +
-                "    ExcludeColumn: c2_x2\n" +
-                "    IncludeProcedure: p5\n" +
-                "    ExcludeProcedure: p6\n" +
-                "  IncludeTable: t7\n" +
-                "    IncludeColumn: c71\n" +
-                "    ExcludeColumn: c72\n" +
-                "  ExcludeTable: t8\n" +
-                "  IncludeColumn: c_xxx1\n" +
-                "  ExcludeColumn: c_xxx2\n" +
-                "  IncludeProcedure: p7\n" +
-                "  ExcludeProcedure: p8\n", engineering.toString());
-
-
-        builder.compact();
-        assertEquals(
-                "ReverseEngineering: \n" +
-                        "  Catalog: cat_01\n" +
-                        "    Schema: sch_01\n" +
-                        "      IncludeTable: t1\n" +
-                        "        IncludeColumn: c11\n" +
-                        "        IncludeColumn: c_xxx1\n" +
-                        "        IncludeColumn: c_xx1\n" +
-                        "        IncludeColumn: c_x1\n" +
-                        "        ExcludeColumn: c12\n" +
-                        "        ExcludeColumn: c_xxx2\n" +
-                        "        ExcludeColumn: c_xx2\n" +
-                        "        ExcludeColumn: c_x2\n" +
-                        "      IncludeTable: t7\n" +
-                        "        IncludeColumn: c71\n" +
-                        "        IncludeColumn: c_xxx1\n" +
-                        "        ExcludeColumn: c72\n" +
-                        "        ExcludeColumn: c_xxx2\n" +
-                        "      IncludeTable: t3\n" +
-                        "        IncludeColumn: c31\n" +
-                        "        IncludeColumn: c_xxx1\n" +
-                        "        IncludeColumn: c_xx1\n" +
-                        "        ExcludeColumn: c32\n" +
-                        "        ExcludeColumn: c_xxx2\n" +
-                        "        ExcludeColumn: c_xx2\n" +
-                        "      ExcludeTable: t2\n" +
-                        "      ExcludeTable: t8\n" +
-                        "      ExcludeTable: t4\n" +
-                        "      IncludeProcedure: p1\n" +
-                        "      IncludeProcedure: p7\n" +
-                        "      IncludeProcedure: p3\n" +
-                        "      ExcludeProcedure: p2\n" +
-                        "      ExcludeProcedure: p8\n" +
-                        "      ExcludeProcedure: p4\n" +
-                        "    Schema: sch_02\n" +
-                        "      IncludeTable: t5\n" +
-                        "        IncludeColumn: c51\n" +
-                        "        IncludeColumn: c_xxx1\n" +
-                        "        IncludeColumn: c2_x1\n" +
-                        "        ExcludeColumn: c52\n" +
-                        "        ExcludeColumn: c_xxx2\n" +
-                        "        ExcludeColumn: c2_x2\n" +
-                        "      IncludeTable: t7\n" +
-                        "        IncludeColumn: c71\n" +
-                        "        IncludeColumn: c_xxx1\n" +
-                        "        ExcludeColumn: c72\n" +
-                        "        ExcludeColumn: c_xxx2\n" +
-                        "      ExcludeTable: t6\n" +
-                        "      ExcludeTable: t8\n" +
-                        "      IncludeProcedure: p5\n" +
-                        "      IncludeProcedure: p7\n" +
-                        "      ExcludeProcedure: p6\n" +
-                        "      ExcludeProcedure: p8\n", engineering.toString());
-    }
-
-    protected IncludeTable includeTable(String name, String incCol, String excCol) {
-        IncludeTable incTable01 = new IncludeTable(name);
-        incTable01.addIncludeColumn(new IncludeColumn(incCol));
-        incTable01.addExcludeColumn(new ExcludeColumn(excCol));
-        return incTable01;
-    }
-
-    /*@Test
-    public void testEmptyDbEntitiesFilters() throws Exception {
-        ReverseEngineering engineering = new ReverseEngineering();
-        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
-
-        assertEquals("If nothing was configured we have to import everything. Filter %/%/% true/true/true",
-                new FiltersConfig(eFilters(path(), TRUE, TRUE, NULL)),
-                executions);
-    }
-
-    @Test
-    public void testOnlyOneCatalogDbEntitiesFilters() throws Exception {
-        ReverseEngineering engineering = new ReverseEngineering();
-        engineering.addCatalog(new Catalog("catalog_01"));
-        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
-
-
-        assertEquals(new FiltersConfig(eFilters(path("catalog_01", null), TRUE, TRUE, NULL)),
-                executions);
-    }
-
-    @Test
-    public void testCatalogDbEntitiesFilters() throws Exception {
-        ReverseEngineering engineering = new ReverseEngineering();
-        engineering.addCatalog(new Catalog("catalog_01"));
-        engineering.addCatalog(new Catalog("catalog_02").schema(new Schema("schema_01")));
-        engineering.addCatalog(new Catalog("catalog_02").schema(new Schema("schema_02")));
-        engineering.addCatalog(new Catalog("catalog_02").schema(new Schema("schema_03")));
-        engineering.addCatalog(new Catalog("catalog_03").schema(new Schema("schema_01")));
-        engineering.addCatalog(new Catalog("catalog_03").schema(new Schema("schema_01")));
-        engineering.addCatalog(new Catalog("catalog_03").schema(new Schema("schema_01")));
-        engineering.addCatalog(new Catalog("catalog_03").schema(new Schema("schema_01")));
-        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
-
-
-        assertEquals(new FiltersConfig(
-                        eFilters(path("catalog_01", null), TRUE, TRUE, NULL),
-                        eFilters(path("catalog_02", "schema_01"), TRUE, TRUE, NULL),
-                        eFilters(path("catalog_02", "schema_02"), TRUE, TRUE, NULL),
-                        eFilters(path("catalog_02", "schema_03"), TRUE, TRUE, NULL),
-                        eFilters(path("catalog_03", "schema_01"), TRUE, TRUE, NULL)
-                ),
-                executions);
-    }
-
-    @Test
-    public void testSchemaDbEntitiesFilters() throws Exception {
-        ReverseEngineering engineering = new ReverseEngineering();
-        engineering.addSchema(new Schema("schema_01"));
-        engineering.addSchema(new Schema("schema_02"));
-        engineering.addSchema(new Schema("schema_03"));
-        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
-
-
-        assertEquals(new FiltersConfig(
-                        eFilters(path(null, "schema_01"), TRUE, TRUE, NULL),
-                        eFilters(path(null, "schema_02"), TRUE, TRUE, NULL),
-                        eFilters(path(null, "schema_03"), TRUE, TRUE, NULL)
-                ),
-                executions);
-    }
-
-    @Test
-    public void testFiltersDbEntitiesFilters() throws Exception {
-        ReverseEngineering engineering = new ReverseEngineering();
-        engineering.addIncludeTable(new IncludeTable("IncludeTable"));
-        engineering.addIncludeColumn(new IncludeColumn("IncludeColumn"));
-        engineering.addIncludeProcedure(new IncludeProcedure("IncludeProcedure"));
-        engineering.addExcludeTable(new ExcludeTable("ExcludeTable"));
-        engineering.addExcludeColumn(new ExcludeColumn("ExcludeColumn"));
-        engineering.addExcludeProcedure(new ExcludeProcedure("ExcludeProcedure"));
-
-        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
-
-        assertEquals(new FiltersConfig(
-                        eFilters(path(),
-                            list(include("IncludeTable"), exclude("ExcludeTable")),
-                            list(include("IncludeColumn"), exclude("ExcludeColumn")),
-                            list(include("IncludeProcedure"), exclude("ExcludeProcedure"))),
-                        eFilters(path(null, null, "IncludeTable"), NULL, TRUE, NULL)
-                ),
-                executions);
-    }
-
-    @Test
-    public void testComplexConfiguration() throws Exception {
-        IncludeTable table = new IncludeTable("table");
-        table.addIncludeColumn(new IncludeColumn("column"));
-
-        Schema schema = new Schema("schema");
-        schema.addIncludeTable(table);
-
-        Catalog catalog = new Catalog("catalog");
-        catalog.addSchema(schema);
-
-        ReverseEngineering engineering = new ReverseEngineering();
-        engineering.addCatalog(catalog);
-
-        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
-
-        assertEquals(new FiltersConfig(
-                        eFilters(path("catalog", "schema"), include("table"), NULL, NULL),
-                        eFilters(path("catalog", "schema", "table"), NULL, include("column"), NULL)
-                        ),
-                executions);
-    }
-
-    @Test
-    public void testAddNull() throws Exception {
-        FiltersConfigBuilder builder = new FiltersConfigBuilder(new ReverseEngineering());
-        DbPath path = new DbPath();
-        builder.add(new EntityFilters(path, NULL, NULL, NULL));
-        builder.add(new EntityFilters(path, NULL, NULL, NULL));
-        builder.add(new EntityFilters(path, NULL, NULL, NULL));
-        builder.add(new EntityFilters(path, NULL, NULL, NULL));
-
-        EntityFilters filter = builder.filtersConfig().filter(path);
-        assertFalse(filter.isEmpty());
-    }*/
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/AddColumnToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/AddColumnToModelIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/AddColumnToModelIT.java
deleted file mode 100644
index db60769..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/AddColumnToModelIT.java
+++ /dev/null
@@ -1,98 +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.merge;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.sql.Types;
-import java.util.List;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.junit.Test;
-
-public class AddColumnToModelIT extends MergeCase {
-
-    @Test
-    public void testAddColumn() throws Exception {
-        dropTableIfPresent("NEW_TABLE");
-        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);
-
-        map.addDbEntity(dbEntity);
-
-        assertTokensAndExecute(1, 0);
-        assertTokensAndExecute(0, 0);
-
-        ObjEntity objEntity = new ObjEntity("NewTable");
-        objEntity.setDbEntity(dbEntity);
-        ObjAttribute oatr1 = new ObjAttribute("name");
-        oatr1.setDbAttributePath(column2.getName());
-        oatr1.setType("java.lang.String");
-        objEntity.addAttribute(oatr1);
-        map.addObjEntity(objEntity);
-
-        // remove name column
-        objEntity.removeAttribute(oatr1.getName());
-        dbEntity.removeAttribute(column2.getName());
-        assertNull(objEntity.getAttribute(oatr1.getName()));
-        assertEquals(0, objEntity.getAttributes().size());
-        assertNull(dbEntity.getAttribute(column2.getName()));
-
-        List<MergerToken> tokens = createMergeTokens();
-        assertEquals(1, tokens.size());
-        MergerToken token = tokens.get(0);
-        if (token.getDirection().isToDb()) {
-            token = token.createReverse(mergerFactory());
-        }
-        assertTrue(token instanceof AddColumnToModel);
-        execute(token);
-        assertEquals(1, objEntity.getAttributes().size());
-        assertEquals("java.lang.String", objEntity.getAttributes().iterator()
-                .next().getType());
-
-        // clear up
-        map.removeObjEntity(objEntity.getName(), true);
-        map.removeDbEntity(dbEntity.getName(), true);
-        resolver.refreshMappingCache();
-        assertNull(map.getObjEntity(objEntity.getName()));
-        assertNull(map.getDbEntity(dbEntity.getName()));
-        assertFalse(map.getDbEntities().contains(dbEntity));
-
-        assertTokensAndExecute(1, 0);
-        assertTokensAndExecute(0, 0);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/CreateTableToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/CreateTableToModelIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/CreateTableToModelIT.java
deleted file mode 100644
index 3d14c1d..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/CreateTableToModelIT.java
+++ /dev/null
@@ -1,97 +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.merge;
-
-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 java.sql.Types;
-import java.util.List;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
-import org.junit.Test;
-
-public class CreateTableToModelIT extends MergeCase {
-
-	@Test
-	public void testAddTable() throws Exception {
-		dropTableIfPresent("NEW_TABLE");
-		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);
-
-		// for the new entity to the db
-		execute(mergerFactory().createCreateTableToDb(dbEntity));
-
-		List<MergerToken> tokens = createMergeTokens();
-		assertEquals(1, tokens.size());
-		MergerToken token = tokens.get(0);
-		if (token.getDirection().isToDb()) {
-			token = token.createReverse(mergerFactory());
-		}
-		assertTrue(token.getClass().getName(), token instanceof CreateTableToModel);
-
-		execute(token);
-
-		ObjEntity objEntity = null;
-		for (ObjEntity candidate : map.getObjEntities()) {
-			if (dbEntity.getName().equalsIgnoreCase(candidate.getDbEntityName())) {
-				objEntity = candidate;
-				break;
-			}
-		}
-		assertNotNull(objEntity);
-
-		assertEquals(objEntity.getClassName(), map.getDefaultPackage() + "." + objEntity.getName());
-		assertEquals(objEntity.getSuperClassName(), map.getDefaultSuperclass());
-		assertEquals(objEntity.getClientClassName(), map.getDefaultClientPackage() + "." + objEntity.getName());
-		assertEquals(objEntity.getClientSuperClassName(), map.getDefaultClientSuperclass());
-
-		assertEquals(1, objEntity.getAttributes().size());
-		assertEquals("java.lang.String", objEntity.getAttributes().iterator().next().getType());
-
-		// clear up
-		// fix psql case issue
-		map.removeDbEntity(objEntity.getDbEntity().getName(), true);
-		map.removeObjEntity(objEntity.getName(), true);
-		map.removeDbEntity(dbEntity.getName(), true);
-		resolver.refreshMappingCache();
-		assertNull(map.getObjEntity(objEntity.getName()));
-		assertNull(map.getDbEntity(dbEntity.getName()));
-		assertFalse(map.getDbEntities().contains(dbEntity));
-
-		assertTokensAndExecute(1, 0);
-		assertTokensAndExecute(0, 0);
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/DbMergerTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/DbMergerTest.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/DbMergerTest.java
deleted file mode 100644
index 894129d..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/DbMergerTest.java
+++ /dev/null
@@ -1,261 +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.merge;
-
-import org.apache.cayenne.access.loader.DbLoaderConfiguration;
-import org.apache.cayenne.dba.hsqldb.HSQLMergerFactory;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.merge.builders.DbEntityBuilder;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.apache.cayenne.merge.builders.ObjectMother.dataMap;
-import static org.apache.cayenne.merge.builders.ObjectMother.dbAttr;
-import static org.apache.cayenne.merge.builders.ObjectMother.dbEntity;
-import static org.junit.Assert.assertEquals;
-
-public class DbMergerTest {
-
-    @Test
-    public void testEmptyDataMap() throws Exception {
-        assertEquals(0, dbMerger().createMergeTokens(new ArrayList<DbEntity>(0),
-                new ArrayList<DbEntity>(0), new DbLoaderConfiguration()).size());
-    }
-
-    @Test
-    public void testAddTable() throws Exception {
-        DbEntityBuilder dbEntity =
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt()
-        );
-        DataMap existing = dataMap().with(dbEntity).build();
-
-        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
-                new ArrayList<DbEntity>(0), new DbLoaderConfiguration());
-
-        assertEquals(1, tokens.size());
-        assertEquals(factory().createCreateTableToDb(dbEntity.build()).getTokenValue(),
-                     tokens.get(0).getTokenValue());
-    }
-
-    @Test
-    public void testRemoveTable() throws Exception {
-        DataMap db = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt()
-        )).build();
-
-        List<MergerToken> tokens = dbMerger().createMergeTokens(new ArrayList<DbEntity>(0),
-                db.getDbEntities(), new DbLoaderConfiguration());
-
-        assertEquals(1, tokens.size());
-        assertEquals(factory().createDropTableToDb(db.getDbEntity("table1")).getTokenValue(),
-                     tokens.get(0).getTokenValue());
-    }
-
-    @Test
-    public void testAddColumn() throws Exception {
-        DataMap existing = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt(),
-                dbAttr("attr02").typeInt()
-        )).build();
-
-        DataMap db = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt()
-        )).build();
-
-        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
-                db.getDbEntities(), new DbLoaderConfiguration());
-
-        assertEquals(1, tokens.size());
-
-        DbEntity entity = existing.getDbEntity("table1");
-        assertEquals(factory().createAddColumnToDb(entity, entity.getAttribute("attr02")).getTokenValue(),
-                     tokens.get(0).getTokenValue());
-    }
-
-    @Test
-    public void testAddRelationship() throws Exception {
-        DataMap existing = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt(),
-                dbAttr("attr02").typeInt()),
-
-            dbEntity("table2").attributes(
-                dbAttr("attr01").typeInt().primaryKey(),
-                dbAttr("attr02").typeInt())
-        ).join("rel", "table1.attr01", "table2.attr01")
-         .build();
-
-        DataMap db = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt(),
-                dbAttr("attr02").typeInt()),
-
-            dbEntity("table2").attributes(
-                dbAttr("attr01").typeInt().primaryKey(),
-                dbAttr("attr02").typeInt())
-        )//.join("table1.attr01", "table2.attr01")
-         .build();
-
-
-        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
-                db.getDbEntities(), new DbLoaderConfiguration());
-
-        assertEquals(1, tokens.size());
-
-        DbEntity entity = existing.getDbEntity("table1");
-        assertEquals(factory().createAddRelationshipToDb(entity, entity.getRelationship("rel")).getTokenValue(),
-                     tokens.get(0).getTokenValue());
-    }
-
-    @Test
-    public void testAddRelationship1() throws Exception {
-        DataMap existing = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt(),
-                dbAttr("attr02").typeInt()),
-
-            dbEntity("table2").attributes(
-                dbAttr("attr01").typeInt().primaryKey(),
-                dbAttr("attr02").typeInt().primaryKey(),
-                dbAttr("attr03").typeInt().primaryKey())
-        ).join("rel", "table1.attr01", "table2.attr01")
-         .join("rel1", "table1.attr01", "table2.attr03")
-         .build();
-
-        DataMap db = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt(),
-                dbAttr("attr02").typeInt()),
-
-            dbEntity("table2").attributes(
-                dbAttr("attr01").typeInt().primaryKey(),
-                dbAttr("attr02").typeInt().primaryKey(),
-                dbAttr("attr03").typeInt().primaryKey())
-        ).join("rel", "table1.attr01", "table2.attr02")
-         .join("rel1", "table1.attr01", "table2.attr03")
-         .build();
-
-
-        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
-                db.getDbEntities(), new DbLoaderConfiguration());
-
-        assertEquals(2, tokens.size());
-
-        DbEntity entity = existing.getDbEntity("table1");
-        assertEquals(factory().createDropRelationshipToDb(entity, entity.getRelationship("rel")).getTokenValue(),
-                     tokens.get(0).getTokenValue());
-
-        entity = db.getDbEntity("table1");
-        assertEquals(factory().createAddRelationshipToDb(entity, entity.getRelationship("rel")).getTokenValue(),
-                     tokens.get(0).getTokenValue());
-    }
-
-    @Test
-    public void testRemoveRelationship() throws Exception {
-        DataMap existing = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt(),
-                dbAttr("attr02").typeInt()),
-
-            dbEntity("table2").attributes(
-                dbAttr("attr01").typeInt().primaryKey(),
-                dbAttr("attr02").typeInt())
-        )
-         .build();
-
-        DataMap db = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt(),
-                dbAttr("attr02").typeInt()),
-
-            dbEntity("table2").attributes(
-                dbAttr("attr01").typeInt().primaryKey(),
-                dbAttr("attr02").typeInt())
-        ).join("rel", "table1.attr01", "table2.attr01")
-         .build();
-
-
-        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(), db.getDbEntities(), new DbLoaderConfiguration());
-
-        assertEquals(1, tokens.size());
-
-        DbEntity entity = db.getDbEntity("table1");
-        assertEquals(factory().createDropRelationshipToDb(entity, entity.getRelationship("rel")).getTokenValue(),
-                     tokens.get(0).getTokenValue());
-    }
-
-    @Test
-    public void testRemoveColumn() throws Exception {
-        DataMap existing = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt()
-        )).build();
-
-        DataMap db = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt(),
-                dbAttr("attr02").typeInt()
-        )).build();
-
-        List<MergerToken> tokens = dbMerger().createMergeTokens(existing.getDbEntities(),
-                db.getDbEntities(), new DbLoaderConfiguration());
-
-        assertEquals(1, tokens.size());
-
-        DbEntity entity = db.getDbEntity("table1");
-        assertEquals(factory().createDropColumnToModel(entity, entity.getAttribute("attr02")).getTokenValue(),
-                     tokens.get(0).getTokenValue());
-    }
-
-    @Test
-    public void testNoChanges() throws Exception {
-        DataMap dataMap1 = dataMap().with(
-                dbEntity("table1").attributes(
-                        dbAttr("attr01").typeInt(),
-                        dbAttr("attr02").typeInt(),
-                        dbAttr("attr03").typeInt()
-                )).build();
-
-        DataMap dataMap2 = dataMap().with(
-            dbEntity("table1").attributes(
-                dbAttr("attr01").typeInt(),
-                dbAttr("attr02").typeInt(),
-                dbAttr("attr03").typeInt()
-        )).build();
-
-
-        assertEquals(0, dbMerger().createMergeTokens(dataMap1.getDbEntities(),
-                dataMap2.getDbEntities(), new DbLoaderConfiguration()).size());
-    }
-
-    private DbMerger dbMerger() {
-        return new DbMerger(factory());
-    }
-
-    private HSQLMergerFactory factory() {
-        return new HSQLMergerFactory();
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/DropColumnToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/DropColumnToModelIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/DropColumnToModelIT.java
deleted file mode 100644
index 9005e79..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/DropColumnToModelIT.java
+++ /dev/null
@@ -1,235 +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.merge;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.sql.Types;
-import java.util.List;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.junit.Test;
-
-public class DropColumnToModelIT extends MergeCase {
-
-	@Test
-	public void testSimpleColumn() throws Exception {
-		dropTableIfPresent("NEW_TABLE");
-
-		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);
-
-		map.addDbEntity(dbEntity);
-
-		assertTokensAndExecute(1, 0);
-		assertTokensAndExecute(0, 0);
-
-		ObjEntity objEntity = new ObjEntity("NewTable");
-		objEntity.setDbEntity(dbEntity);
-		ObjAttribute oatr1 = new ObjAttribute("name");
-		oatr1.setDbAttributePath(column2.getName());
-		oatr1.setType("java.lang.String");
-		objEntity.addAttribute(oatr1);
-		map.addObjEntity(objEntity);
-
-		// force drop name column in db
-		MergerToken token = mergerFactory().createDropColumnToDb(dbEntity, column2);
-		execute(token);
-
-		List<MergerToken> tokens = createMergeTokens();
-		assertEquals(1, tokens.size());
-		token = tokens.get(0);
-		if (token.getDirection().isToDb()) {
-			token = token.createReverse(mergerFactory());
-		}
-		assertTrue(token instanceof DropColumnToModel);
-		execute(token);
-		assertNull(dbEntity.getAttribute(column2.getName()));
-		assertNull(objEntity.getAttribute(oatr1.getName()));
-
-		// clear up
-		map.removeObjEntity(objEntity.getName(), true);
-		map.removeDbEntity(dbEntity.getName(), true);
-		resolver.refreshMappingCache();
-		assertNull(map.getObjEntity(objEntity.getName()));
-		assertNull(map.getDbEntity(dbEntity.getName()));
-		assertFalse(map.getDbEntities().contains(dbEntity));
-
-		assertTokensAndExecute(1, 0);
-		assertTokensAndExecute(0, 0);
-	}
-
-	@Test
-	public void testRemoveFKColumnWithoutRelationshipInDb() throws Exception {
-		dropTableIfPresent("NEW_TABLE");
-		dropTableIfPresent("NEW_TABLE2");
-
-		assertTokensAndExecute(0, 0);
-
-		DbEntity dbEntity1 = new DbEntity("NEW_TABLE");
-
-		DbAttribute e1col1 = new DbAttribute("ID", Types.INTEGER, dbEntity1);
-		e1col1.setMandatory(true);
-		e1col1.setPrimaryKey(true);
-		dbEntity1.addAttribute(e1col1);
-
-		DbAttribute e1col2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity1);
-		e1col2.setMaxLength(10);
-		e1col2.setMandatory(false);
-		dbEntity1.addAttribute(e1col2);
-
-		map.addDbEntity(dbEntity1);
-
-		DbEntity dbEntity2 = new DbEntity("NEW_TABLE2");
-		DbAttribute e2col1 = new DbAttribute("ID", Types.INTEGER, dbEntity2);
-		e2col1.setMandatory(true);
-		e2col1.setPrimaryKey(true);
-		dbEntity2.addAttribute(e2col1);
-		DbAttribute e2col2 = new DbAttribute("FK", Types.INTEGER, dbEntity2);
-		dbEntity2.addAttribute(e2col2);
-		DbAttribute e2col3 = new DbAttribute("NAME", Types.VARCHAR, dbEntity2);
-		e2col3.setMaxLength(10);
-		dbEntity2.addAttribute(e2col3);
-
-		map.addDbEntity(dbEntity2);
-
-		assertTokensAndExecute(2, 0);
-		assertTokensAndExecute(0, 0);
-
-		// force drop fk column in db
-		execute(mergerFactory().createDropColumnToDb(dbEntity2, e2col2));
-
-		// create db relationships, but do not sync them to db
-		DbRelationship rel1To2 = new DbRelationship("rel1To2");
-		rel1To2.setSourceEntity(dbEntity1);
-		rel1To2.setTargetEntityName(dbEntity2);
-		rel1To2.setToMany(true);
-		rel1To2.addJoin(new DbJoin(rel1To2, e1col1.getName(), e2col2.getName()));
-		dbEntity1.addRelationship(rel1To2);
-		DbRelationship rel2To1 = new DbRelationship("rel2To1");
-		rel2To1.setSourceEntity(dbEntity2);
-		rel2To1.setTargetEntityName(dbEntity1);
-		rel2To1.setToMany(false);
-		rel2To1.addJoin(new DbJoin(rel2To1, e2col2.getName(), e1col1.getName()));
-		dbEntity2.addRelationship(rel2To1);
-		assertSame(rel1To2, rel2To1.getReverseRelationship());
-		assertSame(rel2To1, rel1To2.getReverseRelationship());
-
-		// create ObjEntities
-		ObjEntity objEntity1 = new ObjEntity("NewTable");
-		objEntity1.setDbEntity(dbEntity1);
-		ObjAttribute oatr1 = new ObjAttribute("name");
-		oatr1.setDbAttributePath(e1col2.getName());
-		oatr1.setType("java.lang.String");
-		objEntity1.addAttribute(oatr1);
-		map.addObjEntity(objEntity1);
-		ObjEntity objEntity2 = new ObjEntity("NewTable2");
-		objEntity2.setDbEntity(dbEntity2);
-		ObjAttribute o2a1 = new ObjAttribute("name");
-		o2a1.setDbAttributePath(e2col3.getName());
-		o2a1.setType("java.lang.String");
-		objEntity2.addAttribute(o2a1);
-		map.addObjEntity(objEntity2);
-
-		// create ObjRelationships
-		assertEquals(0, objEntity1.getRelationships().size());
-		assertEquals(0, objEntity2.getRelationships().size());
-		ObjRelationship objRel1To2 = new ObjRelationship("objRel1To2");
-		objRel1To2.addDbRelationship(rel1To2);
-		objRel1To2.setSourceEntity(objEntity1);
-		objRel1To2.setTargetEntityName(objEntity2);
-		objEntity1.addRelationship(objRel1To2);
-		ObjRelationship objRel2To1 = new ObjRelationship("objRel2To1");
-		objRel2To1.addDbRelationship(rel2To1);
-		objRel2To1.setSourceEntity(objEntity2);
-		objRel2To1.setTargetEntityName(objEntity1);
-		objEntity2.addRelationship(objRel2To1);
-		assertEquals(1, objEntity1.getRelationships().size());
-		assertEquals(1, objEntity2.getRelationships().size());
-		assertSame(objRel1To2, objRel2To1.getReverseRelationship());
-		assertSame(objRel2To1, objRel1To2.getReverseRelationship());
-
-		// try do use the merger to remove the column and relationship in the
-		// model
-		List<MergerToken> tokens = createMergeTokens();
-		assertTokens(tokens, 2, 0);
-		// TODO: reversing the following two tokens should also reverse the
-		// order
-		MergerToken token0 = tokens.get(0).createReverse(mergerFactory());
-		MergerToken token1 = tokens.get(1).createReverse(mergerFactory());
-		if (!(token0 instanceof DropRelationshipToModel && token1 instanceof DropColumnToModel || token1 instanceof DropRelationshipToModel
-				&& token0 instanceof DropColumnToModel)) {
-			fail();
-		}
-		// do not execute DropRelationshipToModel, only DropColumnToModel.
-		if (token1 instanceof DropColumnToModel) {
-			execute(token1);
-		} else {
-			execute(token0);
-		}
-
-		// check after merging
-		assertNull(dbEntity2.getAttribute(e2col2.getName()));
-		assertEquals(0, dbEntity1.getRelationships().size());
-		assertEquals(0, dbEntity2.getRelationships().size());
-		assertEquals(0, objEntity1.getRelationships().size());
-		assertEquals(0, objEntity2.getRelationships().size());
-
-		// clear up
-
-		dbEntity1.removeRelationship(rel1To2.getName());
-		dbEntity2.removeRelationship(rel2To1.getName());
-		map.removeObjEntity(objEntity1.getName(), true);
-		map.removeDbEntity(dbEntity1.getName(), true);
-		map.removeObjEntity(objEntity2.getName(), true);
-		map.removeDbEntity(dbEntity2.getName(), true);
-		resolver.refreshMappingCache();
-		assertNull(map.getObjEntity(objEntity1.getName()));
-		assertNull(map.getDbEntity(dbEntity1.getName()));
-		assertNull(map.getObjEntity(objEntity2.getName()));
-		assertNull(map.getDbEntity(dbEntity2.getName()));
-		assertFalse(map.getDbEntities().contains(dbEntity1));
-		assertFalse(map.getDbEntities().contains(dbEntity2));
-
-		assertTokensAndExecute(2, 0);
-		assertTokensAndExecute(0, 0);
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index 4610fcd..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/DropRelationshipToModelIT.java
+++ /dev/null
@@ -1,188 +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.merge;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.fail;
-
-import java.sql.Types;
-import java.util.List;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.junit.Test;
-
-public class DropRelationshipToModelIT extends MergeCase {
-
-	@Test
-	public void testForeignKey() throws Exception {
-		dropTableIfPresent("NEW_TABLE");
-		dropTableIfPresent("NEW_TABLE2");
-
-		assertTokensAndExecute(0, 0);
-
-		DbEntity dbEntity1 = new DbEntity("NEW_TABLE");
-
-		DbAttribute e1col1 = new DbAttribute("ID", Types.INTEGER, dbEntity1);
-		e1col1.setMandatory(true);
-		e1col1.setPrimaryKey(true);
-		dbEntity1.addAttribute(e1col1);
-
-		DbAttribute e1col2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity1);
-		e1col2.setMaxLength(10);
-		e1col2.setMandatory(false);
-		dbEntity1.addAttribute(e1col2);
-
-		map.addDbEntity(dbEntity1);
-
-		DbEntity dbEntity2 = new DbEntity("NEW_TABLE2");
-		DbAttribute e2col1 = new DbAttribute("ID", Types.INTEGER, dbEntity2);
-		e2col1.setMandatory(true);
-		e2col1.setPrimaryKey(true);
-		dbEntity2.addAttribute(e2col1);
-		DbAttribute e2col2 = new DbAttribute("FK", Types.INTEGER, dbEntity2);
-		dbEntity2.addAttribute(e2col2);
-		DbAttribute e2col3 = new DbAttribute("NAME", Types.VARCHAR, dbEntity2);
-		e2col3.setMaxLength(10);
-		dbEntity2.addAttribute(e2col3);
-
-		map.addDbEntity(dbEntity2);
-
-		// create db relationships
-		DbRelationship rel1To2 = new DbRelationship("rel1To2");
-		rel1To2.setSourceEntity(dbEntity1);
-		rel1To2.setTargetEntityName(dbEntity2);
-		rel1To2.setToMany(true);
-		rel1To2.addJoin(new DbJoin(rel1To2, e1col1.getName(), e2col2.getName()));
-		dbEntity1.addRelationship(rel1To2);
-		DbRelationship rel2To1 = new DbRelationship("rel2To1");
-		rel2To1.setSourceEntity(dbEntity2);
-		rel2To1.setTargetEntityName(dbEntity1);
-		rel2To1.setToMany(false);
-		rel2To1.addJoin(new DbJoin(rel2To1, e2col2.getName(), e1col1.getName()));
-		dbEntity2.addRelationship(rel2To1);
-		assertSame(rel1To2, rel2To1.getReverseRelationship());
-		assertSame(rel2To1, rel1To2.getReverseRelationship());
-
-		assertTokensAndExecute(4, 0);
-		assertTokensAndExecute(0, 0);
-
-		// create ObjEntities
-		ObjEntity objEntity1 = new ObjEntity("NewTable");
-		objEntity1.setDbEntity(dbEntity1);
-		ObjAttribute oatr1 = new ObjAttribute("name");
-		oatr1.setDbAttributePath(e1col2.getName());
-		oatr1.setType("java.lang.String");
-		objEntity1.addAttribute(oatr1);
-		map.addObjEntity(objEntity1);
-		ObjEntity objEntity2 = new ObjEntity("NewTable2");
-		objEntity2.setDbEntity(dbEntity2);
-		ObjAttribute o2a1 = new ObjAttribute("name");
-		o2a1.setDbAttributePath(e2col3.getName());
-		o2a1.setType("java.lang.String");
-		objEntity2.addAttribute(o2a1);
-		map.addObjEntity(objEntity2);
-
-		// create ObjRelationships
-		assertEquals(0, objEntity1.getRelationships().size());
-		assertEquals(0, objEntity2.getRelationships().size());
-		ObjRelationship objRel1To2 = new ObjRelationship("objRel1To2");
-		objRel1To2.addDbRelationship(rel1To2);
-		objRel1To2.setSourceEntity(objEntity1);
-		objRel1To2.setTargetEntityName(objEntity2);
-		objEntity1.addRelationship(objRel1To2);
-		ObjRelationship objRel2To1 = new ObjRelationship("objRel2To1");
-		objRel2To1.addDbRelationship(rel2To1);
-		objRel2To1.setSourceEntity(objEntity2);
-		objRel2To1.setTargetEntityName(objEntity1);
-		objEntity2.addRelationship(objRel2To1);
-		assertEquals(1, objEntity1.getRelationships().size());
-		assertEquals(1, objEntity2.getRelationships().size());
-		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();
-        /**
-         * Add Relationship NEW_TABLE->NEW_TABLE2 To Model
-         * Drop Relationship NEW_TABLE2->NEW_TABLE To DB
-         * Drop Column NEW_TABLE2.FK To DB
-         * */
-        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);
-
-		// try do use the merger to remove the relationship in the model
-		tokens = createMergeTokens();
-		assertTokens(tokens, 2, 0);
-		// TODO: reversing the following two tokens should also reverse the
-		// order
-		MergerToken token0 = tokens.get(0).createReverse(mergerFactory());
-		MergerToken token1 = tokens.get(1).createReverse(mergerFactory());
-		if (!(token0 instanceof DropRelationshipToModel && token1 instanceof DropColumnToModel || token1 instanceof DropRelationshipToModel
-				&& token0 instanceof DropColumnToModel)) {
-			fail();
-		}
-		execute(token0);
-		execute(token1);
-
-		// check after merging
-		assertNull(dbEntity2.getAttribute(e2col2.getName()));
-		assertEquals(0, dbEntity1.getRelationships().size());
-		assertEquals(0, dbEntity2.getRelationships().size());
-		assertEquals(0, objEntity1.getRelationships().size());
-		assertEquals(0, objEntity2.getRelationships().size());
-
-		// clear up
-		dbEntity1.removeRelationship(rel1To2.getName());
-		dbEntity2.removeRelationship(rel2To1.getName());
-		map.removeObjEntity(objEntity1.getName(), true);
-		map.removeDbEntity(dbEntity1.getName(), true);
-		map.removeObjEntity(objEntity2.getName(), true);
-		map.removeDbEntity(dbEntity2.getName(), true);
-		resolver.refreshMappingCache();
-		assertNull(map.getObjEntity(objEntity1.getName()));
-		assertNull(map.getDbEntity(dbEntity1.getName()));
-		assertNull(map.getObjEntity(objEntity2.getName()));
-		assertNull(map.getDbEntity(dbEntity2.getName()));
-		assertFalse(map.getDbEntities().contains(dbEntity1));
-		assertFalse(map.getDbEntities().contains(dbEntity2));
-
-		assertTokensAndExecute(2, 0);
-		assertTokensAndExecute(0, 0);
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/DropTableToModelIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/DropTableToModelIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/DropTableToModelIT.java
deleted file mode 100644
index ddf5a49..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/DropTableToModelIT.java
+++ /dev/null
@@ -1,94 +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.merge;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.sql.Types;
-import java.util.List;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.junit.Test;
-
-public class DropTableToModelIT extends MergeCase {
-
-	@Test
-	public void testDropTable() throws Exception {
-		dropTableIfPresent("NEW_TABLE");
-		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);
-
-		map.addDbEntity(dbEntity);
-
-		assertTokensAndExecute(1, 0);
-		assertTokensAndExecute(0, 0);
-
-		ObjEntity objEntity = new ObjEntity("NewTable");
-		objEntity.setDbEntity(dbEntity);
-		ObjAttribute oatr1 = new ObjAttribute("name");
-		oatr1.setDbAttributePath(column2.getName());
-		oatr1.setType("java.lang.String");
-		objEntity.addAttribute(oatr1);
-		map.addObjEntity(objEntity);
-
-		// force drop table in db
-		MergerToken token = mergerFactory().createDropTableToDb(dbEntity);
-		execute(token);
-
-		List<MergerToken> tokens = createMergeTokens();
-		assertEquals(1, tokens.size());
-		token = tokens.get(0);
-		if (token.getDirection().isToDb()) {
-			token = token.createReverse(mergerFactory());
-		}
-		assertTrue(token instanceof DropTableToModel);
-		execute(token);
-		resolver.refreshMappingCache();
-		assertNull(map.getDbEntity(dbEntity.getName()));
-		assertNull(map.getObjEntity(objEntity.getName()));
-
-		// clear up
-		map.removeObjEntity(objEntity.getName(), true);
-		map.removeDbEntity(dbEntity.getName(), true);
-		resolver.refreshMappingCache();
-		assertNull(map.getObjEntity(objEntity.getName()));
-		assertNull(map.getDbEntity(dbEntity.getName()));
-		assertFalse(map.getDbEntities().contains(dbEntity));
-
-		assertTokensAndExecute(0, 0);
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index dfbffa9..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/MergeCase.java
+++ /dev/null
@@ -1,215 +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.merge;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.access.loader.DbLoaderConfiguration;
-import org.apache.cayenne.access.loader.filters.FiltersConfig;
-import org.apache.cayenne.access.loader.filters.PatternFilter;
-import org.apache.cayenne.access.loader.filters.TableFilter;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.test.jdbc.DBHelper;
-import org.apache.cayenne.unit.UnitDbAdapter;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.junit.Before;
-
-import java.sql.Connection;
-import java.sql.Statement;
-import java.sql.Types;
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public abstract class MergeCase extends ServerCase {
-
-	private Log logger = LogFactory.getLog(MergeCase.class);
-
-	@Inject
-	private DBHelper dbHelper;
-
-	@Inject
-	private ServerRuntime runtime;
-
-	@Inject
-	private UnitDbAdapter accessStackAdapter;
-
-	@Inject
-	private ServerCaseDataSourceFactory dataSourceFactory;
-
-	@Inject
-	protected EntityResolver resolver;
-
-	@Inject
-	protected DataNode node;
-
-	protected DataMap map;
-
-	@Override
-	public void cleanUpDB() throws Exception {
-		dbHelper.update("ARTGROUP").set("PARENT_GROUP_ID", null, Types.INTEGER).execute();
-		super.cleanUpDB();
-	}
-
-	@Before
-	public void setUp() throws Exception {
-
-		// this map can't be safely modified in this test, as it is reset by DI
-		// container
-		// on every test
-		map = runtime.getDataDomain().getDataMap("testmap");
-
-		filterDataMap();
-
-		List<MergerToken> tokens = createMergeTokens();
-		execute(tokens);
-
-		assertTokensAndExecute(0, 0);
-	}
-
-	protected DbMerger createMerger(MergerFactory mergerFactory) {
-		return createMerger(mergerFactory, null);
-	}
-
-	protected DbMerger createMerger(MergerFactory mergerFactory, ValueForNullProvider valueForNullProvider) {
-		return new DbMerger(mergerFactory, valueForNullProvider);
-	}
-
-	protected List<MergerToken> createMergeTokens() {
-		DbLoaderConfiguration loaderConfiguration = new DbLoaderConfiguration();
-		loaderConfiguration.setFiltersConfig(FiltersConfig.create(null, null,
-				TableFilter.include("ARTIST|GALLERY|PAINTING|NEW_TABLE2?"), PatternFilter.INCLUDE_NOTHING));
-
-		return createMerger(node.getAdapter().mergerFactory()).createMergeTokens(node.getDataSource(),
-				node.getAdapter(), map, loaderConfiguration);
-	}
-
-	/**
-	 * Remote binary pk {@link DbEntity} for {@link DbAdapter} not supporting
-	 * that and so on.
-	 */
-	private void filterDataMap() {
-		// copied from AbstractAccessStack.dbEntitiesInInsertOrder
-		boolean excludeBinPK = accessStackAdapter.supportsBinaryPK();
-
-		if (!excludeBinPK) {
-			return;
-		}
-
-		List<DbEntity> entitiesToRemove = new ArrayList<DbEntity>();
-
-		for (DbEntity ent : map.getDbEntities()) {
-			for (DbAttribute attr : ent.getAttributes()) {
-				// check for BIN PK or FK to BIN Pk
-				if (attr.getType() == Types.BINARY || attr.getType() == Types.VARBINARY
-						|| attr.getType() == Types.LONGVARBINARY) {
-
-					if (attr.isPrimaryKey() || attr.isForeignKey()) {
-						entitiesToRemove.add(ent);
-						break;
-					}
-				}
-			}
-		}
-
-		for (DbEntity e : entitiesToRemove) {
-			map.removeDbEntity(e.getName(), true);
-		}
-	}
-
-	protected void execute(List<MergerToken> tokens) {
-		MergerContext mergerContext = MergerContext.builder(map).dataNode(node).build();
-		for (MergerToken tok : tokens) {
-			tok.execute(mergerContext);
-		}
-	}
-
-	protected void execute(MergerToken token) throws Exception {
-		MergerContext mergerContext = MergerContext.builder(map).dataNode(node).build();
-		token.execute(mergerContext);
-	}
-
-	private void executeSql(String sql) throws Exception {
-
-		try (Connection conn = dataSourceFactory.getSharedDataSource().getConnection();) {
-
-			try (Statement st = conn.createStatement();) {
-				st.execute(sql);
-			}
-		}
-	}
-
-	protected void assertTokens(List<MergerToken> tokens, int expectedToDb, int expectedToModel) {
-		int actualToDb = 0;
-		int actualToModel = 0;
-		for (MergerToken token : tokens) {
-			if (token.getDirection().isToDb()) {
-				actualToDb++;
-			} else if (token.getDirection().isToModel()) {
-				actualToModel++;
-			}
-		}
-
-		assertEquals("tokens to db", expectedToDb, actualToDb);
-		assertEquals("tokens to model", expectedToModel, actualToModel);
-	}
-
-	protected void assertTokensAndExecute(int expectedToDb, int expectedToModel) {
-		List<MergerToken> tokens = createMergeTokens();
-		assertTokens(tokens, expectedToDb, expectedToModel);
-		execute(tokens);
-	}
-
-	protected MergerFactory mergerFactory() {
-		return node.getAdapter().mergerFactory();
-	}
-
-	protected void dropTableIfPresent(String tableName) throws Exception {
-
-		// must have a dummy datamap for the dummy table for the downstream code
-		// to work
-		DataMap map = new DataMap("dummy");
-		map.setQuotingSQLIdentifiers(map.isQuotingSQLIdentifiers());
-		DbEntity entity = new DbEntity(tableName);
-		map.addDbEntity(entity);
-
-		AbstractToDbToken t = (AbstractToDbToken) mergerFactory().createDropTableToDb(entity);
-
-		for (String sql : t.createSql(node.getAdapter())) {
-
-			try {
-				executeSql(sql);
-			} catch (Exception e) {
-				logger.info("Exception dropping table " + tableName + ", probably abscent..");
-			}
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index 5c4c51b..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/MergerFactoryIT.java
+++ /dev/null
@@ -1,310 +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.merge;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import java.sql.Types;
-
-import org.apache.cayenne.CayenneDataObject;
-import org.apache.cayenne.access.DataContext;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.junit.Test;
-
-public class MergerFactoryIT extends MergeCase {
-
-    @Inject
-    private DataContext context;
-
-    @Test
-    public void testAddAndDropColumnToDb() throws Exception {
-        DbEntity dbEntity = map.getDbEntity("PAINTING");
-        assertNotNull(dbEntity);
-
-        // create and add new column to model and db
-        DbAttribute column = new DbAttribute("NEWCOL1", Types.VARCHAR, dbEntity);
-
-        column.setMandatory(false);
-        column.setMaxLength(10);
-        dbEntity.addAttribute(column);
-        assertTokensAndExecute(1, 0);
-
-        // try merge once more to check that is was merged
-        assertTokensAndExecute(0, 0);
-
-        // remove it from model and db
-        dbEntity.removeAttribute(column.getName());
-        assertTokensAndExecute(1, 0);
-        assertTokensAndExecute(0, 0);
-    }
-
-    @Test
-    public void testChangeVarcharSizeToDb() throws Exception {
-        DbEntity dbEntity = map.getDbEntity("PAINTING");
-        assertNotNull(dbEntity);
-
-        // create and add new column to model and db
-        DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
-
-        column.setMandatory(false);
-        column.setMaxLength(10);
-        dbEntity.addAttribute(column);
-        assertTokensAndExecute(1, 0);
-
-        // check that is was merged
-        assertTokensAndExecute(0, 0);
-
-        // change size
-        column.setMaxLength(20);
-
-        // merge to db
-        assertTokensAndExecute(1, 0);
-
-        // check that is was merged
-        assertTokensAndExecute(0, 0);
-
-        // clean up
-        dbEntity.removeAttribute(column.getName());
-        assertTokensAndExecute(1, 0);
-        assertTokensAndExecute(0, 0);
-    }
-
-    @Test
-    public void testMultipleTokensToDb() throws Exception {
-        DbEntity dbEntity = map.getDbEntity("PAINTING");
-        assertNotNull(dbEntity);
-
-        DbAttribute column1 = new DbAttribute("NEWCOL3", Types.VARCHAR, dbEntity);
-        column1.setMandatory(false);
-        column1.setMaxLength(10);
-        dbEntity.addAttribute(column1);
-        DbAttribute column2 = new DbAttribute("NEWCOL4", Types.VARCHAR, dbEntity);
-        column2.setMandatory(false);
-        column2.setMaxLength(10);
-        dbEntity.addAttribute(column2);
-
-        assertTokensAndExecute(2, 0);
-
-        // check that is was merged
-        assertTokensAndExecute(0, 0);
-
-        // change size
-        column1.setMaxLength(20);
-        column2.setMaxLength(30);
-
-        // merge to db
-        assertTokensAndExecute(2, 0);
-
-        // check that is was merged
-        assertTokensAndExecute(0, 0);
-
-        // clean up
-        dbEntity.removeAttribute(column1.getName());
-        dbEntity.removeAttribute(column2.getName());
-        assertTokensAndExecute(2, 0);
-        assertTokensAndExecute(0, 0);
-    }
-
-    @Test
-    public void testAddTableToDb() throws Exception {
-        dropTableIfPresent("NEW_TABLE");
-
-        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);
-
-        map.addDbEntity(dbEntity);
-
-        assertTokensAndExecute(1, 0);
-        assertTokensAndExecute(0, 0);
-
-        ObjEntity objEntity = new ObjEntity("NewTable");
-        objEntity.setDbEntity(dbEntity);
-        ObjAttribute oatr1 = new ObjAttribute("name");
-        oatr1.setDbAttributePath(column2.getName());
-        oatr1.setType("java.lang.String");
-        objEntity.addAttribute(oatr1);
-        map.addObjEntity(objEntity);
-
-        for (int i = 0; i < 5; i++) {
-            CayenneDataObject dao = (CayenneDataObject) context.newObject(objEntity
-                    .getName());
-            dao.writeProperty(oatr1.getName(), "test " + i);
-        }
-        context.commitChanges();
-
-        // clear up
-        map.removeObjEntity(objEntity.getName(), true);
-        map.removeDbEntity(dbEntity.getName(), true);
-        resolver.refreshMappingCache();
-        assertNull(map.getObjEntity(objEntity.getName()));
-        assertNull(map.getDbEntity(dbEntity.getName()));
-        assertFalse(map.getDbEntities().contains(dbEntity));
-
-        assertTokensAndExecute(1, 0);
-        assertTokensAndExecute(0, 0);
-    }
-
-    @Test
-    public void testAddForeignKeyWithTable() throws Exception {
-        dropTableIfPresent("NEW_TABLE");
-
-        assertTokensAndExecute(0, 0);
-
-        DbEntity dbEntity = new DbEntity("NEW_TABLE");
-
-        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);
-
-        DbEntity artistDbEntity = map.getDbEntity("ARTIST");
-        assertNotNull(artistDbEntity);
-
-        // relation from new_table to artist
-        DbRelationship r1 = new DbRelationship("toArtistR1");
-        r1.setSourceEntity(dbEntity);
-        r1.setTargetEntityName(artistDbEntity);
-        r1.setToMany(false);
-        r1.addJoin(new DbJoin(r1, "ARTIST_ID", "ARTIST_ID"));
-        dbEntity.addRelationship(r1);
-
-        // relation from artist to new_table
-        DbRelationship r2 = new DbRelationship("toNewTableR2");
-        r2.setSourceEntity(artistDbEntity);
-        r2.setTargetEntityName(dbEntity);
-        r2.setToMany(true);
-        r2.addJoin(new DbJoin(r2, "ARTIST_ID", "ARTIST_ID"));
-        artistDbEntity.addRelationship(r2);
-
-        assertTokensAndExecute(2, 0);
-        assertTokensAndExecute(0, 0);
-
-        // remove relationships
-        dbEntity.removeRelationship(r1.getName());
-        artistDbEntity.removeRelationship(r2.getName());
-        resolver.refreshMappingCache();
-        /*
-         * Db -Rel 'toArtistR1' - NEW_TABLE 1 -> 1 ARTIST"
-r2 =     * Db -Rel 'toNewTableR2' - ARTIST 1 -> * NEW_TABLE"
-         * */
-        assertTokensAndExecute(1, 1);
-        assertTokensAndExecute(0, 0);
-
-        // clear up
-        // map.removeObjEntity(objEntity.getName(), true);
-        map.removeDbEntity(dbEntity.getName(), true);
-        resolver.refreshMappingCache();
-        // assertNull(map.getObjEntity(objEntity.getName()));
-        assertNull(map.getDbEntity(dbEntity.getName()));
-        assertFalse(map.getDbEntities().contains(dbEntity));
-
-        assertTokensAndExecute(1, 0);
-        assertTokensAndExecute(0, 0);
-    }
-
-    @Test
-    public void testAddForeignKeyAfterTable() throws Exception {
-        dropTableIfPresent("NEW_TABLE");
-
-        assertTokensAndExecute(0, 0);
-
-        DbEntity dbEntity = new DbEntity("NEW_TABLE");
-        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);
-
-        DbEntity artistDbEntity = map.getDbEntity("ARTIST");
-        assertNotNull(artistDbEntity);
-
-        assertTokensAndExecute(1, 0);
-        assertTokensAndExecute(0, 0);
-
-        // relation from new_table to artist
-        DbRelationship r1 = new DbRelationship("toArtistR1");
-        r1.setSourceEntity(dbEntity);
-        r1.setTargetEntityName(artistDbEntity);
-        r1.setToMany(false);
-        r1.addJoin(new DbJoin(r1, "ARTIST_ID", "ARTIST_ID"));
-        dbEntity.addRelationship(r1);
-
-        // relation from artist to new_table
-        DbRelationship r2 = new DbRelationship("toNewTableR2");
-        r2.setSourceEntity(artistDbEntity);
-        r2.setTargetEntityName(dbEntity);
-        r2.setToMany(true);
-        r2.addJoin(new DbJoin(r2, "ARTIST_ID", "ARTIST_ID"));
-        artistDbEntity.addRelationship(r2);
-
-        assertTokensAndExecute(1, 0);
-        assertTokensAndExecute(0, 0);
-
-        // remove relationships
-        dbEntity.removeRelationship(r1.getName());
-        artistDbEntity.removeRelationship(r2.getName());
-        resolver.refreshMappingCache();
-        /*
-        * Add Relationship ARTIST->NEW_TABLE To Model
-        * Drop Relationship NEW_TABLE->ARTIST To DB
-        * */
-        assertTokensAndExecute(1, 1);
-        assertTokensAndExecute(0, 0);
-
-        // clear up
-        // map.removeObjEntity(objEntity.getName(), true);
-        map.removeDbEntity(dbEntity.getName(), true);
-        resolver.refreshMappingCache();
-        // assertNull(map.getObjEntity(objEntity.getName()));
-        assertNull(map.getDbEntity(dbEntity.getName()));
-        assertFalse(map.getDbEntities().contains(dbEntity));
-
-        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/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/SetAllowNullToDbIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/SetAllowNullToDbIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/SetAllowNullToDbIT.java
deleted file mode 100644
index 580b38a..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/SetAllowNullToDbIT.java
+++ /dev/null
@@ -1,66 +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.merge;
-
-import static org.junit.Assert.assertNotNull;
-
-import java.sql.Types;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.junit.Test;
-
-public class SetAllowNullToDbIT extends MergeCase {
-
-	@Test
-	public void test() throws Exception {
-		DbEntity dbEntity = map.getDbEntity("PAINTING");
-		assertNotNull(dbEntity);
-
-		// create and add new column to model and db
-		DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
-
-		try {
-
-			column.setMandatory(true);
-			column.setMaxLength(10);
-			dbEntity.addAttribute(column);
-			assertTokensAndExecute(2, 0);
-
-			// check that is was merged
-			assertTokensAndExecute(0, 0);
-
-			// set null
-			column.setMandatory(false);
-
-			// merge to db
-			assertTokensAndExecute(1, 0);
-
-			// check that is was merged
-			assertTokensAndExecute(0, 0);
-
-			// clean up
-		} finally {
-			dbEntity.removeAttribute(column.getName());
-			assertTokensAndExecute(1, 0);
-			assertTokensAndExecute(0, 0);
-		}
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/SetNotNullToDbIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/SetNotNullToDbIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/SetNotNullToDbIT.java
deleted file mode 100644
index f78c77f..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/SetNotNullToDbIT.java
+++ /dev/null
@@ -1,62 +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.merge;
-
-import static org.junit.Assert.assertNotNull;
-
-import java.sql.Types;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.junit.Test;
-
-public class SetNotNullToDbIT extends MergeCase {
-
-	@Test
-	public void test() throws Exception {
-		DbEntity dbEntity = map.getDbEntity("PAINTING");
-		assertNotNull(dbEntity);
-
-		// create and add new column to model and db
-		DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
-
-		column.setMandatory(false);
-		column.setMaxLength(10);
-		dbEntity.addAttribute(column);
-		assertTokensAndExecute(1, 0);
-
-		// check that is was merged
-		assertTokensAndExecute(0, 0);
-
-		// set not null
-		column.setMandatory(true);
-
-		// merge to db
-		assertTokensAndExecute(1, 0);
-
-		// check that is was merged
-		assertTokensAndExecute(0, 0);
-
-		// clean up
-		dbEntity.removeAttribute(column.getName());
-		assertTokensAndExecute(1, 0);
-		assertTokensAndExecute(0, 0);
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/SetPrimaryKeyToDbIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/SetPrimaryKeyToDbIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/SetPrimaryKeyToDbIT.java
deleted file mode 100644
index 3003b07..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/SetPrimaryKeyToDbIT.java
+++ /dev/null
@@ -1,58 +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.merge;
-
-import java.sql.Types;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.junit.Test;
-
-public class SetPrimaryKeyToDbIT extends MergeCase {
-
-	@Test
-	public void test() throws Exception {
-		dropTableIfPresent("NEW_TABLE");
-		assertTokensAndExecute(0, 0);
-
-		DbEntity dbEntity1 = new DbEntity("NEW_TABLE");
-
-		DbAttribute e1col1 = new DbAttribute("ID1", Types.INTEGER, dbEntity1);
-		e1col1.setMandatory(true);
-		e1col1.setPrimaryKey(true);
-		dbEntity1.addAttribute(e1col1);
-		map.addDbEntity(dbEntity1);
-
-		assertTokensAndExecute(1, 0);
-		assertTokensAndExecute(0, 0);
-
-		DbAttribute e1col2 = new DbAttribute("ID2", Types.INTEGER, dbEntity1);
-		e1col2.setMandatory(true);
-		dbEntity1.addAttribute(e1col2);
-
-		assertTokensAndExecute(2, 0);
-		assertTokensAndExecute(0, 0);
-
-		e1col1.setPrimaryKey(false);
-		e1col2.setPrimaryKey(true);
-
-		assertTokensAndExecute(1, 0);
-		assertTokensAndExecute(0, 0);
-	}
-}


[09/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderPartialIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderPartialIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderPartialIT.java
new file mode 100644
index 0000000..5490ef9
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderPartialIT.java
@@ -0,0 +1,116 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+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.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class DbLoaderPartialIT extends ServerCase {
+
+    @Inject
+    private DbAdapter adapter;
+
+    @Inject
+    private ServerCaseDataSourceFactory dataSourceFactory;
+
+    private DbLoader loader;
+
+    @Before
+    public void setUp() throws Exception {
+        loader = new DbLoader(
+                dataSourceFactory.getSharedDataSource().getConnection(),
+                adapter,
+                new DefaultDbLoaderDelegate());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        loader.getConnection().close();
+    }
+
+    /**
+     * Tests that FKs are properly loaded when the relationship source is not loaded. See
+     * CAY-479. This test will perform two reverse engineers. The second reverse engineer
+     * will skip two tables that share relationships with PAINTING. Relationships in
+     * ARTIST and GALLERY should remain unmodified, and all PAINTING relationships should
+     * be loaded.
+     */
+    @Test
+    public void testPartialLoad() throws Exception {
+
+        DataMap map = new DataMap();
+        String tableLabel = adapter.tableTypeForTable();
+
+        loader.loadDataMapFromDB(null, "%", new String[] {tableLabel}, map);
+
+        Collection<?> rels = getDbEntity(map, "ARTIST").getRelationships();
+        assertNotNull(rels);
+        int artistRels = rels.size();
+
+        rels = getDbEntity(map, "GALLERY").getRelationships();
+        assertNotNull(rels);
+        int galleryRels = rels.size();
+
+        rels = getDbEntity(map, "PAINTING").getRelationships();
+        assertNotNull(rels);
+        int paintingRels = rels.size();
+
+        loader.loadDataMapFromDB(null, "%", new String[] {
+            tableLabel
+        }, map);
+
+        rels = getDbEntity(map, "ARTIST").getRelationships();
+        assertNotNull(rels);
+        assertEquals(artistRels, rels.size());
+
+        rels = getDbEntity(map, "GALLERY").getRelationships();
+        assertNotNull(rels);
+        assertEquals(galleryRels, rels.size());
+
+        rels = getDbEntity(map, "PAINTING").getRelationships();
+        assertNotNull(rels);
+        assertEquals(paintingRels, rels.size());
+    }
+
+    private DbEntity getDbEntity(DataMap map, String name) {
+        DbEntity de = map.getDbEntity(name);
+        // sometimes table names get converted to lowercase
+        if (de == null) {
+            de = map.getDbEntity(name.toLowerCase());
+        }
+
+        return de;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/FiltersConfigBuilderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/FiltersConfigBuilderTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/FiltersConfigBuilderTest.java
new file mode 100644
index 0000000..6d945a5
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/FiltersConfigBuilderTest.java
@@ -0,0 +1,391 @@
+/*
+ * 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.dbsync.reverse;
+
+import org.apache.cayenne.dbimport.Catalog;
+import org.apache.cayenne.dbimport.ExcludeColumn;
+import org.apache.cayenne.dbimport.ExcludeProcedure;
+import org.apache.cayenne.dbimport.ExcludeTable;
+import org.apache.cayenne.dbimport.IncludeColumn;
+import org.apache.cayenne.dbimport.IncludeProcedure;
+import org.apache.cayenne.dbimport.IncludeTable;
+import org.apache.cayenne.dbimport.ReverseEngineering;
+import org.apache.cayenne.dbimport.Schema;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class FiltersConfigBuilderTest {
+
+    @Test
+    public void testCompact_01() {
+        ReverseEngineering engineering = new ReverseEngineering();
+        engineering.addIncludeTable(new IncludeTable("table1"));
+        engineering.addIncludeTable(new IncludeTable("table2"));
+        engineering.addIncludeTable(new IncludeTable("table3"));
+
+        engineering.addIncludeColumn(new IncludeColumn("includeColumn"));
+
+        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
+        builder.compact();
+        assertEquals(
+                "ReverseEngineering: \n" +
+                "  Catalog: null\n" +
+                "    Schema: null\n" +
+                "      IncludeTable: table1\n" +
+                "        IncludeColumn: includeColumn\n" +
+                "      IncludeTable: table2\n" +
+                "        IncludeColumn: includeColumn\n" +
+                "      IncludeTable: table3\n" +
+                "        IncludeColumn: includeColumn\n", engineering.toString());
+    }
+
+    @Test
+    public void testCompact_02() {
+        ReverseEngineering engineering = new ReverseEngineering();
+        engineering.addCatalog(new Catalog("catalogName"));
+        engineering.addSchema(new Schema("schemaName01"));
+        engineering.addSchema(new Schema("schemaName02"));
+
+        engineering.addIncludeTable(new IncludeTable("table1"));
+        engineering.addExcludeTable(new ExcludeTable("table2"));
+
+        engineering.addIncludeColumn(new IncludeColumn("includeColumn"));
+
+        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
+        builder.compact();
+        assertEquals(
+                "ReverseEngineering: \n" +
+                "  Catalog: catalogName\n" +
+                "    Schema: schemaName01\n" +
+                "      IncludeTable: table1\n" +
+                "        IncludeColumn: includeColumn\n" +
+                "      ExcludeTable: table2\n" +
+                "    Schema: schemaName02\n" +
+                "      IncludeTable: table1\n" +
+                "        IncludeColumn: includeColumn\n" +
+                "      ExcludeTable: table2\n", engineering.toString());
+    }
+
+    @Test
+    public void testCompact_03() {
+        ReverseEngineering engineering = new ReverseEngineering();
+        engineering.addCatalog(new Catalog("APP1"));
+        engineering.addCatalog(new Catalog("APP2"));
+
+        engineering.addExcludeTable(new ExcludeTable("SYS_.*"));
+        engineering.addExcludeColumn(new ExcludeColumn("calculated_.*"));
+
+        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
+        builder.compact();
+        assertEquals(
+                "ReverseEngineering: \n" +
+                "  Catalog: APP1\n" +
+                "    Schema: null\n" +
+                "      IncludeTable: null\n" +
+                "        ExcludeColumn: calculated_.*\n" +
+                "      ExcludeTable: SYS_.*\n" +
+                "  Catalog: APP2\n" +
+                "    Schema: null\n" +
+                "      IncludeTable: null\n" +
+                "        ExcludeColumn: calculated_.*\n" +
+                "      ExcludeTable: SYS_.*\n", engineering.toString());
+    }
+
+    @Test
+    public void testCompact_04() {
+        ReverseEngineering engineering = new ReverseEngineering();
+        engineering.addSchema(new Schema("s"));
+
+        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
+        builder.compact();
+        assertEquals(
+                "ReverseEngineering: \n" +
+                "  Catalog: null\n" +
+                "    Schema: s\n" +
+                "      IncludeTable: null\n", engineering.toString());
+    }
+
+    @Test
+    public void testCompact_full() {
+        ReverseEngineering engineering = new ReverseEngineering();
+        Catalog cat01 = new Catalog("cat_01");
+
+        Schema sch01 = new Schema("sch_01");
+
+        sch01.addIncludeTable(includeTable("t1", "c11", "c12"));
+        sch01.addExcludeTable(new ExcludeTable("t2"));
+        sch01.addIncludeProcedure(new IncludeProcedure("p1"));
+        sch01.addExcludeProcedure(new ExcludeProcedure("p2"));
+        sch01.addIncludeColumn(new IncludeColumn("c_x1"));
+        sch01.addExcludeColumn(new ExcludeColumn("c_x2"));
+
+        cat01.addSchema(sch01);
+
+        cat01.addIncludeTable(includeTable("t3", "c31", "c32"));
+        cat01.addExcludeTable(new ExcludeTable("t4"));
+        cat01.addIncludeProcedure(new IncludeProcedure("p3"));
+        cat01.addExcludeProcedure(new ExcludeProcedure("p4"));
+        cat01.addIncludeColumn(new IncludeColumn("c_xx1"));
+        cat01.addExcludeColumn(new ExcludeColumn("c_xx2"));
+
+        engineering.addCatalog(cat01);
+
+        Schema sch02 = new Schema("sch_02");
+
+        sch02.addIncludeTable(includeTable("t5", "c51", "c52"));
+        sch02.addExcludeTable(new ExcludeTable("t6"));
+        sch02.addIncludeProcedure(new IncludeProcedure("p5"));
+        sch02.addExcludeProcedure(new ExcludeProcedure("p6"));
+        sch02.addIncludeColumn(new IncludeColumn("c2_x1"));
+        sch02.addExcludeColumn(new ExcludeColumn("c2_x2"));
+
+        engineering.addSchema(sch02);
+
+        engineering.addIncludeTable(includeTable("t7", "c71", "c72"));
+        engineering.addExcludeTable(new ExcludeTable("t8"));
+        engineering.addIncludeProcedure(new IncludeProcedure("p7"));
+        engineering.addExcludeProcedure(new ExcludeProcedure("p8"));
+        engineering.addIncludeColumn(new IncludeColumn("c_xxx1"));
+        engineering.addExcludeColumn(new ExcludeColumn("c_xxx2"));
+
+        FiltersConfigBuilder builder = new FiltersConfigBuilder(engineering);
+        assertEquals("Original ReverseEngineering should be",
+                "ReverseEngineering: \n" +
+                "  Catalog: cat_01\n" +
+                "    Schema: sch_01\n" +
+                "      IncludeTable: t1\n" +
+                "        IncludeColumn: c11\n" +
+                "        ExcludeColumn: c12\n" +
+                "      ExcludeTable: t2\n" +
+                "      IncludeColumn: c_x1\n" +
+                "      ExcludeColumn: c_x2\n" +
+                "      IncludeProcedure: p1\n" +
+                "      ExcludeProcedure: p2\n" +
+                "    IncludeTable: t3\n" +
+                "      IncludeColumn: c31\n" +
+                "      ExcludeColumn: c32\n" +
+                "    ExcludeTable: t4\n" +
+                "    IncludeColumn: c_xx1\n" +
+                "    ExcludeColumn: c_xx2\n" +
+                "    IncludeProcedure: p3\n" +
+                "    ExcludeProcedure: p4\n" +
+                "  Schema: sch_02\n" +
+                "    IncludeTable: t5\n" +
+                "      IncludeColumn: c51\n" +
+                "      ExcludeColumn: c52\n" +
+                "    ExcludeTable: t6\n" +
+                "    IncludeColumn: c2_x1\n" +
+                "    ExcludeColumn: c2_x2\n" +
+                "    IncludeProcedure: p5\n" +
+                "    ExcludeProcedure: p6\n" +
+                "  IncludeTable: t7\n" +
+                "    IncludeColumn: c71\n" +
+                "    ExcludeColumn: c72\n" +
+                "  ExcludeTable: t8\n" +
+                "  IncludeColumn: c_xxx1\n" +
+                "  ExcludeColumn: c_xxx2\n" +
+                "  IncludeProcedure: p7\n" +
+                "  ExcludeProcedure: p8\n", engineering.toString());
+
+
+        builder.compact();
+        assertEquals(
+                "ReverseEngineering: \n" +
+                        "  Catalog: cat_01\n" +
+                        "    Schema: sch_01\n" +
+                        "      IncludeTable: t1\n" +
+                        "        IncludeColumn: c11\n" +
+                        "        IncludeColumn: c_xxx1\n" +
+                        "        IncludeColumn: c_xx1\n" +
+                        "        IncludeColumn: c_x1\n" +
+                        "        ExcludeColumn: c12\n" +
+                        "        ExcludeColumn: c_xxx2\n" +
+                        "        ExcludeColumn: c_xx2\n" +
+                        "        ExcludeColumn: c_x2\n" +
+                        "      IncludeTable: t7\n" +
+                        "        IncludeColumn: c71\n" +
+                        "        IncludeColumn: c_xxx1\n" +
+                        "        ExcludeColumn: c72\n" +
+                        "        ExcludeColumn: c_xxx2\n" +
+                        "      IncludeTable: t3\n" +
+                        "        IncludeColumn: c31\n" +
+                        "        IncludeColumn: c_xxx1\n" +
+                        "        IncludeColumn: c_xx1\n" +
+                        "        ExcludeColumn: c32\n" +
+                        "        ExcludeColumn: c_xxx2\n" +
+                        "        ExcludeColumn: c_xx2\n" +
+                        "      ExcludeTable: t2\n" +
+                        "      ExcludeTable: t8\n" +
+                        "      ExcludeTable: t4\n" +
+                        "      IncludeProcedure: p1\n" +
+                        "      IncludeProcedure: p7\n" +
+                        "      IncludeProcedure: p3\n" +
+                        "      ExcludeProcedure: p2\n" +
+                        "      ExcludeProcedure: p8\n" +
+                        "      ExcludeProcedure: p4\n" +
+                        "    Schema: sch_02\n" +
+                        "      IncludeTable: t5\n" +
+                        "        IncludeColumn: c51\n" +
+                        "        IncludeColumn: c_xxx1\n" +
+                        "        IncludeColumn: c2_x1\n" +
+                        "        ExcludeColumn: c52\n" +
+                        "        ExcludeColumn: c_xxx2\n" +
+                        "        ExcludeColumn: c2_x2\n" +
+                        "      IncludeTable: t7\n" +
+                        "        IncludeColumn: c71\n" +
+                        "        IncludeColumn: c_xxx1\n" +
+                        "        ExcludeColumn: c72\n" +
+                        "        ExcludeColumn: c_xxx2\n" +
+                        "      ExcludeTable: t6\n" +
+                        "      ExcludeTable: t8\n" +
+                        "      IncludeProcedure: p5\n" +
+                        "      IncludeProcedure: p7\n" +
+                        "      ExcludeProcedure: p6\n" +
+                        "      ExcludeProcedure: p8\n", engineering.toString());
+    }
+
+    protected IncludeTable includeTable(String name, String incCol, String excCol) {
+        IncludeTable incTable01 = new IncludeTable(name);
+        incTable01.addIncludeColumn(new IncludeColumn(incCol));
+        incTable01.addExcludeColumn(new ExcludeColumn(excCol));
+        return incTable01;
+    }
+
+    /*@Test
+    public void testEmptyDbEntitiesFilters() throws Exception {
+        ReverseEngineering engineering = new ReverseEngineering();
+        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
+
+        assertEquals("If nothing was configured we have to import everything. Filter %/%/% true/true/true",
+                new FiltersConfig(eFilters(path(), TRUE, TRUE, NULL)),
+                executions);
+    }
+
+    @Test
+    public void testOnlyOneCatalogDbEntitiesFilters() throws Exception {
+        ReverseEngineering engineering = new ReverseEngineering();
+        engineering.addCatalog(new Catalog("catalog_01"));
+        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
+
+
+        assertEquals(new FiltersConfig(eFilters(path("catalog_01", null), TRUE, TRUE, NULL)),
+                executions);
+    }
+
+    @Test
+    public void testCatalogDbEntitiesFilters() throws Exception {
+        ReverseEngineering engineering = new ReverseEngineering();
+        engineering.addCatalog(new Catalog("catalog_01"));
+        engineering.addCatalog(new Catalog("catalog_02").schema(new Schema("schema_01")));
+        engineering.addCatalog(new Catalog("catalog_02").schema(new Schema("schema_02")));
+        engineering.addCatalog(new Catalog("catalog_02").schema(new Schema("schema_03")));
+        engineering.addCatalog(new Catalog("catalog_03").schema(new Schema("schema_01")));
+        engineering.addCatalog(new Catalog("catalog_03").schema(new Schema("schema_01")));
+        engineering.addCatalog(new Catalog("catalog_03").schema(new Schema("schema_01")));
+        engineering.addCatalog(new Catalog("catalog_03").schema(new Schema("schema_01")));
+        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
+
+
+        assertEquals(new FiltersConfig(
+                        eFilters(path("catalog_01", null), TRUE, TRUE, NULL),
+                        eFilters(path("catalog_02", "schema_01"), TRUE, TRUE, NULL),
+                        eFilters(path("catalog_02", "schema_02"), TRUE, TRUE, NULL),
+                        eFilters(path("catalog_02", "schema_03"), TRUE, TRUE, NULL),
+                        eFilters(path("catalog_03", "schema_01"), TRUE, TRUE, NULL)
+                ),
+                executions);
+    }
+
+    @Test
+    public void testSchemaDbEntitiesFilters() throws Exception {
+        ReverseEngineering engineering = new ReverseEngineering();
+        engineering.addSchema(new Schema("schema_01"));
+        engineering.addSchema(new Schema("schema_02"));
+        engineering.addSchema(new Schema("schema_03"));
+        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
+
+
+        assertEquals(new FiltersConfig(
+                        eFilters(path(null, "schema_01"), TRUE, TRUE, NULL),
+                        eFilters(path(null, "schema_02"), TRUE, TRUE, NULL),
+                        eFilters(path(null, "schema_03"), TRUE, TRUE, NULL)
+                ),
+                executions);
+    }
+
+    @Test
+    public void testFiltersDbEntitiesFilters() throws Exception {
+        ReverseEngineering engineering = new ReverseEngineering();
+        engineering.addIncludeTable(new IncludeTable("IncludeTable"));
+        engineering.addIncludeColumn(new IncludeColumn("IncludeColumn"));
+        engineering.addIncludeProcedure(new IncludeProcedure("IncludeProcedure"));
+        engineering.addExcludeTable(new ExcludeTable("ExcludeTable"));
+        engineering.addExcludeColumn(new ExcludeColumn("ExcludeColumn"));
+        engineering.addExcludeProcedure(new ExcludeProcedure("ExcludeProcedure"));
+
+        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
+
+        assertEquals(new FiltersConfig(
+                        eFilters(path(),
+                            list(include("IncludeTable"), exclude("ExcludeTable")),
+                            list(include("IncludeColumn"), exclude("ExcludeColumn")),
+                            list(include("IncludeProcedure"), exclude("ExcludeProcedure"))),
+                        eFilters(path(null, null, "IncludeTable"), NULL, TRUE, NULL)
+                ),
+                executions);
+    }
+
+    @Test
+    public void testComplexConfiguration() throws Exception {
+        IncludeTable table = new IncludeTable("table");
+        table.addIncludeColumn(new IncludeColumn("column"));
+
+        Schema schema = new Schema("schema");
+        schema.addIncludeTable(table);
+
+        Catalog catalog = new Catalog("catalog");
+        catalog.addSchema(schema);
+
+        ReverseEngineering engineering = new ReverseEngineering();
+        engineering.addCatalog(catalog);
+
+        FiltersConfig executions = new FiltersConfigBuilder(engineering).filtersConfig();
+
+        assertEquals(new FiltersConfig(
+                        eFilters(path("catalog", "schema"), include("table"), NULL, NULL),
+                        eFilters(path("catalog", "schema", "table"), NULL, include("column"), NULL)
+                        ),
+                executions);
+    }
+
+    @Test
+    public void testAddNull() throws Exception {
+        FiltersConfigBuilder builder = new FiltersConfigBuilder(new ReverseEngineering());
+        DbPath path = new DbPath();
+        builder.add(new EntityFilters(path, NULL, NULL, NULL));
+        builder.add(new EntityFilters(path, NULL, NULL, NULL));
+        builder.add(new EntityFilters(path, NULL, NULL, NULL));
+        builder.add(new EntityFilters(path, NULL, NULL, NULL));
+
+        EntityFilters filter = builder.filtersConfig().filter(path);
+        assertFalse(filter.isEmpty());
+    }*/
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/ManyToManyCandidateEntityTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/ManyToManyCandidateEntityTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/ManyToManyCandidateEntityTest.java
new file mode 100644
index 0000000..edecdfc
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/ManyToManyCandidateEntityTest.java
@@ -0,0 +1,113 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+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 org.junit.Before;
+import org.junit.Test;
+
+import java.net.URL;
+import java.util.ArrayList;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+public class ManyToManyCandidateEntityTest {
+
+    private DataMap map;
+
+    @Before
+    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);
+    }
+
+    @Test
+    public void testMatchingForManyToManyEntity() throws Exception {
+        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
+
+        assertNotNull(ManyToManyCandidateEntity.build(manyToManyEntity));
+    }
+
+    @Test
+    public void testMatchingForNotManyToManyEntity() throws Exception {
+        ObjEntity entity = map.getObjEntity("Table1");
+
+        assertNull(ManyToManyCandidateEntity.build(entity));
+    }
+
+    @Test
+    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/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigTest.java
new file mode 100644
index 0000000..5a27558
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfigTest.java
@@ -0,0 +1,93 @@
+/*****************************************************************
+ *   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.dbsync.reverse.filters;
+
+import junit.framework.TestCase;
+
+import java.util.Collections;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+public class FiltersConfigTest extends TestCase {
+
+    public void testToString_01() {
+        FiltersConfig config = FiltersConfig.create(null, null,
+                TableFilter.everything(), PatternFilter.INCLUDE_EVERYTHING);
+
+        assertEquals("Catalog: null\n" +
+                     "  Schema: null\n" +
+                     "    Tables: \n" +
+                     "      Include: null Columns: ALL\n" +
+                     "    Procedures: ALL\n", config.toString());
+    }
+
+    public void testToString_02() {
+        FiltersConfig config = new FiltersConfig(
+                new CatalogFilter("catalog_01",
+                        new SchemaFilter("schema_11", TableFilter.everything(), PatternFilter.INCLUDE_EVERYTHING)),
+                new CatalogFilter("catalog_02",
+                        new SchemaFilter("schema_21", TableFilter.everything(), PatternFilter.INCLUDE_NOTHING),
+                        new SchemaFilter("schema_22",
+                                new TableFilter(
+                                        includes(new IncludeTableFilter(null, PatternFilter.INCLUDE_NOTHING)),
+                                        excludes("aaa")),
+                                PatternFilter.INCLUDE_NOTHING),
+                        new SchemaFilter("schema_23", TableFilter.include("include"), PatternFilter.INCLUDE_NOTHING)
+                )
+        );
+
+        assertEquals("Catalog: catalog_01\n" +
+                     "  Schema: schema_11\n" +
+                     "    Tables: \n" +
+                     "      Include: null Columns: ALL\n" +
+                     "    Procedures: ALL\n" +
+                     "Catalog: catalog_02\n" +
+                     "  Schema: schema_21\n" +
+                     "    Tables: \n" +
+                     "      Include: null Columns: ALL\n" +
+                     "    Procedures: NONE\n" +
+                     "  Schema: schema_22\n" +
+                     "    Tables: \n" +
+                     "      Include: null Columns: NONE\n" +
+                     "      aaa\n" +
+                     "    Procedures: NONE\n" +
+                     "  Schema: schema_23\n" +
+                     "    Tables: \n" +
+                     "      Include: include Columns: ALL\n" +
+                     "    Procedures: NONE\n", config.toString());
+    }
+
+    private SortedSet<Pattern> excludes(String ... p) {
+        SortedSet<Pattern> patterns = new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR);
+        for (String pattern : p) {
+            patterns.add(PatternFilter.pattern(pattern));
+        }
+        return patterns;
+    }
+
+    protected SortedSet<IncludeTableFilter> includes(IncludeTableFilter ... filters) {
+        SortedSet<IncludeTableFilter> includeTableFilters = new TreeSet<IncludeTableFilter>();
+        Collections.addAll(includeTableFilters, filters);
+
+        return includeTableFilters;
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/IncludeFilterTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/IncludeFilterTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/IncludeFilterTest.java
new file mode 100644
index 0000000..df9312f
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/IncludeFilterTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.dbsync.reverse.filters;
+
+import org.junit.Test;
+
+public class IncludeFilterTest {
+
+    @Test
+    public void testIsInclude() throws Exception {
+//        IncludeFilter filter = new IncludeFilter(pattern("^v_.*$"));
+//        assertTrue(filter.isInclude("v_new_view"));
+//        assertFalse(filter.isInclude("new_view"));
+//        assertFalse(filter.isInclude("view"));
+//        assertFalse(filter.isInclude("girl"));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilterTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilterTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilterTest.java
new file mode 100644
index 0000000..73739ec
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/PatternFilterTest.java
@@ -0,0 +1,78 @@
+/*****************************************************************
+ *   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.dbsync.reverse.filters;
+
+import junit.framework.TestCase;
+
+public class PatternFilterTest extends TestCase {
+
+    public void testInclude() throws Exception {
+        PatternFilter filter = new PatternFilter()
+                .include("aaa")
+                .include("bbb");
+
+        assertTrue(filter.isInclude("aaa"));
+        assertTrue(filter.isInclude("bbb"));
+        assertFalse(filter.isInclude("aaaa"));
+        assertFalse(filter.isInclude("aa"));
+        assertFalse(filter.isInclude("abb"));
+
+        filter = new PatternFilter().include("^v_.*$");
+        assertTrue(filter.isInclude("v_new_view"));
+        assertFalse(filter.isInclude("new_view"));
+        assertFalse(filter.isInclude("view"));
+        assertFalse(filter.isInclude("girl"));
+    }
+
+    public void testExclude() throws Exception {
+        PatternFilter filter = new PatternFilter()
+                .exclude("aaa")
+                .exclude("bbb");
+
+        assertFalse(filter.isInclude("aaa"));
+        assertFalse(filter.isInclude("bbb"));
+        assertTrue(filter.isInclude("aaaa"));
+        assertTrue(filter.isInclude("aa"));
+        assertTrue(filter.isInclude("abb"));
+    }
+
+    public void testIncludeExclude() throws Exception {
+        PatternFilter filter = new PatternFilter()
+                .include("aa.*")
+                .exclude("aaa");
+
+        assertFalse(filter.isInclude("aaa"));
+        assertFalse(filter.isInclude("bbb"));
+        assertTrue(filter.isInclude("aaaa"));
+        assertTrue(filter.isInclude("aa"));
+        assertFalse(filter.isInclude("abb"));
+    }
+
+    public void testIncludeAllFilter() {
+        assertTrue(PatternFilter.INCLUDE_EVERYTHING.isInclude("qwe"));
+        assertTrue(PatternFilter.INCLUDE_EVERYTHING.isInclude(""));
+        assertTrue(PatternFilter.INCLUDE_EVERYTHING.isInclude(null));
+    }
+
+    public void testIncludeNoneFilter() {
+        assertFalse(PatternFilter.INCLUDE_NOTHING.isInclude("qwe"));
+        assertFalse(PatternFilter.INCLUDE_NOTHING.isInclude(""));
+        assertFalse(PatternFilter.INCLUDE_NOTHING.isInclude(null));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/TableFilterTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/TableFilterTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/TableFilterTest.java
new file mode 100644
index 0000000..979b20f
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/filters/TableFilterTest.java
@@ -0,0 +1,91 @@
+/*****************************************************************
+ *   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.dbsync.reverse.filters;
+
+import junit.framework.TestCase;
+
+import java.util.TreeSet;
+import java.util.regex.Pattern;
+
+public class TableFilterTest extends TestCase {
+
+    public void testIncludeEverything() {
+        TableFilter filter = TableFilter.everything();
+
+        assertNotNull(filter.isIncludeTable("table"));
+        assertNotNull(filter.isIncludeTable("aaaa"));
+        assertNotNull(filter.isIncludeTable(""));
+        assertNotNull(filter.isIncludeTable("alex"));
+    }
+
+    public void testInclude() {
+        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
+        includes.add(new IncludeTableFilter("aaa"));
+        includes.add(new IncludeTableFilter("bb"));
+
+        TableFilter filter = new TableFilter(includes, new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR));
+
+        assertNotNull(filter.isIncludeTable("aaa"));
+        assertNull(filter.isIncludeTable("aa"));
+        assertNull(filter.isIncludeTable("aaaa"));
+
+        assertNotNull(filter.isIncludeTable("bb"));
+        assertNull(filter.isIncludeTable(""));
+        assertNull(filter.isIncludeTable("bbbb"));
+    }
+
+
+    public void testExclude() {
+        TreeSet<Pattern> excludes = new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR);
+        excludes.add(Pattern.compile("aaa"));
+        excludes.add(Pattern.compile("bb"));
+
+        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
+        includes.add(new IncludeTableFilter(null, PatternFilter.INCLUDE_EVERYTHING));
+
+        TableFilter filter = new TableFilter(includes, excludes);
+
+        assertNull(filter.isIncludeTable("aaa"));
+        assertNotNull(filter.isIncludeTable("aa"));
+        assertNotNull(filter.isIncludeTable("aaaa"));
+
+        assertNull(filter.isIncludeTable("bb"));
+        assertNotNull(filter.isIncludeTable(""));
+        assertNotNull(filter.isIncludeTable("bbbb"));
+    }
+
+    public void testIncludeExclude() {
+        TreeSet<Pattern> excludes = new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR);
+        excludes.add(Pattern.compile("aaa"));
+        excludes.add(Pattern.compile("bb"));
+
+        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
+        includes.add(new IncludeTableFilter("aa.*"));
+
+        TableFilter filter = new TableFilter(includes, excludes);
+
+        assertNull(filter.isIncludeTable("aaa"));
+        assertNotNull(filter.isIncludeTable("aa"));
+        assertNotNull(filter.isIncludeTable("aaaa"));
+
+        assertNull(filter.isIncludeTable("bb"));
+        assertNull(filter.isIncludeTable(""));
+        assertNull(filter.isIncludeTable("bbbb"));
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/mapper/DbTypeTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/mapper/DbTypeTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/mapper/DbTypeTest.java
new file mode 100644
index 0000000..f2cb475
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/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.dbsync.reverse.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/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncCase.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncCase.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncCase.java
new file mode 100644
index 0000000..f52a1b9
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncCase.java
@@ -0,0 +1,57 @@
+/*****************************************************************
+ *   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.dbsync.unit;
+
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.spi.DefaultScope;
+import org.apache.cayenne.unit.di.DICase;
+import org.apache.cayenne.unit.di.server.DBCleaner;
+import org.apache.cayenne.unit.di.server.SchemaBuilder;
+import org.apache.cayenne.unit.di.server.ServerCaseModule;
+import org.junit.Before;
+
+
+public class DbSyncCase extends DICase {
+
+    private static final Injector injector;
+
+    static {
+        DefaultScope testScope = new DefaultScope();
+        injector = DIBootstrap.createInjector(
+                new ServerCaseModule(testScope),
+                new DbSyncCaseModule(testScope));
+
+        injector.getInstance(SchemaBuilder.class).rebuildSchema();
+    }
+
+    @Inject
+    private DBCleaner dbCleaner;
+
+    @Before
+    public void cleanUpDB() throws Exception {
+        dbCleaner.clean();
+    }
+
+    @Override
+    protected Injector getUnitTestInjector() {
+        return injector;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncCaseModule.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncCaseModule.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncCaseModule.java
new file mode 100644
index 0000000..369cffb
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncCaseModule.java
@@ -0,0 +1,38 @@
+/*****************************************************************
+ *   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.dbsync.unit;
+
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.DefaultScope;
+
+public class DbSyncCaseModule implements Module {
+    protected DefaultScope testScope;
+
+    public DbSyncCaseModule(DefaultScope testScope) {
+        this.testScope = testScope;
+    }
+
+    @Override
+    public void configure(Binder binder) {
+        binder.bind(ServerRuntime.class).toProvider(DbSyncServerRuntimeProvider.class).in(
+                testScope);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncServerRuntimeProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncServerRuntimeProvider.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncServerRuntimeProvider.java
new file mode 100644
index 0000000..34454d7
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/unit/DbSyncServerRuntimeProvider.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   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.dbsync.unit;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.Provider;
+import org.apache.cayenne.unit.UnitDbAdapter;
+import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
+import org.apache.cayenne.unit.di.server.ServerCaseProperties;
+import org.apache.cayenne.unit.di.server.ServerRuntimeProvider;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public class DbSyncServerRuntimeProvider extends ServerRuntimeProvider {
+
+    public DbSyncServerRuntimeProvider(@Inject ServerCaseDataSourceFactory dataSourceFactory,
+                                       @Inject ServerCaseProperties properties,
+                                       @Inject Provider<DbAdapter> dbAdapterProvider,
+                                       @Inject UnitDbAdapter unitDbAdapter) {
+        super(dataSourceFactory, properties, dbAdapterProvider, unitDbAdapter);
+    }
+
+    @Override
+    protected Collection<? extends Module> getExtraModules() {
+        Collection<Module> modules = new ArrayList<>();
+        modules.addAll(super.getExtraModules());
+        modules.add(new CayenneDbSyncModule());
+        return modules;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/cayenne-relationship-optimisation.xml
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/cayenne-relationship-optimisation.xml b/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/cayenne-relationship-optimisation.xml
new file mode 100644
index 0000000..d4fea49
--- /dev/null
+++ b/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/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/2f7b1d53/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/relationship-optimisation.map.xml
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/relationship-optimisation.map.xml b/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/relationship-optimisation.map.xml
new file mode 100644
index 0000000..e68645f
--- /dev/null
+++ b/cayenne-dbsync/src/test/resources/org/apache/cayenne/dbsync/reverse/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/2f7b1d53/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
deleted file mode 100644
index 67477dd..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/DbLoader.java
+++ /dev/null
@@ -1,832 +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 java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-
-import org.apache.cayenne.access.loader.DbAttributesPerSchemaLoader;
-import org.apache.cayenne.access.loader.DbLoaderConfiguration;
-import org.apache.cayenne.access.loader.DbTableLoader;
-import org.apache.cayenne.access.loader.DefaultDbLoaderDelegate;
-import org.apache.cayenne.access.loader.ManyToManyCandidateEntity;
-import org.apache.cayenne.access.loader.filters.CatalogFilter;
-import org.apache.cayenne.access.loader.filters.FiltersConfig;
-import org.apache.cayenne.access.loader.filters.PatternFilter;
-import org.apache.cayenne.access.loader.filters.SchemaFilter;
-import org.apache.cayenne.access.loader.filters.TableFilter;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.DbRelationshipDetected;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.Procedure;
-import org.apache.cayenne.map.ProcedureParameter;
-import org.apache.cayenne.map.naming.DefaultUniqueNameGenerator;
-import org.apache.cayenne.map.naming.ExportedKey;
-import org.apache.cayenne.map.naming.LegacyNameGenerator;
-import org.apache.cayenne.map.naming.NameCheckers;
-import org.apache.cayenne.map.naming.ObjectNameGenerator;
-import org.apache.cayenne.util.EntityMergeSupport;
-import org.apache.cayenne.util.EqualsBuilder;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * Utility class that does reverse engineering of the database. It can create
- * DataMaps using database meta data obtained via JDBC driver.
- */
-public class DbLoader {
-
-	private static final Log LOGGER = LogFactory.getLog(DbLoader.class);
-
-	public static final String WILDCARD = "%";
-	public static final String WILDCARD_PATTERN = ".*";
-
-	private final Connection connection;
-	private final DbAdapter adapter;
-	private final DbLoaderDelegate delegate;
-
-	private boolean creatingMeaningfulPK;
-
-	private DatabaseMetaData metaData;
-
-	/**
-	 * Strategy for choosing names for entities, attributes and relationships
-	 */
-	private ObjectNameGenerator nameGenerator;
-
-	/**
-	 * Creates new DbLoader.
-	 */
-	public DbLoader(Connection connection, DbAdapter adapter, DbLoaderDelegate delegate) {
-		this(connection, adapter, delegate, new LegacyNameGenerator());
-	}
-
-	/**
-	 * Creates new DbLoader with specified naming strategy.
-	 *
-	 * @since 3.0
-	 */
-	public DbLoader(Connection connection, DbAdapter adapter, DbLoaderDelegate delegate, ObjectNameGenerator strategy) {
-		this.adapter = adapter;
-		this.connection = connection;
-		this.delegate = delegate == null ? new DefaultDbLoaderDelegate() : delegate;
-
-		setNameGenerator(strategy);
-	}
-
-	/**
-	 * Returns DatabaseMetaData object associated with this DbLoader.
-	 */
-	private DatabaseMetaData getMetaData() throws SQLException {
-		if (metaData == null) {
-			metaData = connection.getMetaData();
-		}
-		return metaData;
-	}
-
-	/**
-	 * Check if database support schemas.
-	 */
-	protected boolean supportSchemas() throws SQLException {
-		if (metaData == null) {
-			metaData = connection.getMetaData();
-		}
-		return metaData.supportsSchemasInTableDefinitions();
-	}
-
-	/**
-	 * Check if database support catalogs.
-	 */
-	protected boolean supportCatalogs() throws SQLException {
-		if (metaData == null) {
-			metaData = connection.getMetaData();
-		}
-		return metaData.supportsCatalogsInTableDefinitions();
-	}
-
-	/**
-	 * @since 3.0
-	 */
-	public void setCreatingMeaningfulPK(boolean creatingMeaningfulPK) {
-		this.creatingMeaningfulPK = creatingMeaningfulPK;
-	}
-
-	/**
-	 * Returns true if the generator should map all primary key columns as
-	 * ObjAttributes.
-	 *
-	 * @since 3.0
-	 */
-	public boolean isCreatingMeaningfulPK() {
-		return creatingMeaningfulPK;
-	}
-
-	/**
-	 * Returns database connection used by this DbLoader.
-	 *
-	 * @since 3.0
-	 */
-	public Connection getConnection() {
-		return connection;
-	}
-
-	/**
-	 * Returns DbAdapter associated with this DbLoader.
-	 *
-	 * @since 1.1
-	 */
-	public DbAdapter getAdapter() {
-		return adapter;
-	}
-
-	/**
-	 * Retrieves catalogs for the database associated with this DbLoader.
-	 *
-	 * @return List with the catalog names, empty Array if none found.
-	 */
-	public List<String> getCatalogs() throws SQLException {
-		try (ResultSet rs = getMetaData().getCatalogs()) {
-			return getStrings(rs);
-		}
-	}
-
-	/**
-	 * Retrieves the schemas for the database.
-	 *
-	 * @return List with the schema names, empty Array if none found.
-	 */
-	public List<String> getSchemas() throws SQLException {
-
-		try (ResultSet rs = getMetaData().getSchemas()) {
-			return getStrings(rs);
-		}
-	}
-
-	private static List<String> getStrings(ResultSet rs) throws SQLException {
-		List<String> strings = new ArrayList<String>();
-
-		while (rs.next()) {
-			strings.add(rs.getString(1));
-		}
-
-		return strings;
-	}
-
-	/**
-	 * Returns all the table types for the given database. Types may be such as
-	 * Typical types are "TABLE", "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY",
-	 * "LOCAL TEMPORARY", "ALIAS", "SYNONYM"., etc.
-	 *
-	 * @return List of Strings, empty array if nothing found.
-	 */
-	public List<String> getTableTypes() throws SQLException {
-		List<String> types = new ArrayList<String>();
-
-		try (ResultSet rs = getMetaData().getTableTypes();) {
-			while (rs.next()) {
-				types.add(rs.getString("TABLE_TYPE").trim());
-			}
-		}
-
-		return types;
-	}
-
-	/**
-	 * Creates an ObjEntity for each DbEntity in the map.
-	 */
-	public Collection<ObjEntity> loadObjEntities(DataMap map, DbLoaderConfiguration config,
-			Collection<DbEntity> entities) {
-		Collection<ObjEntity> loadedEntities = DbLoader.loadObjEntities(map, config, entities, nameGenerator);
-
-		createEntityMerger(map).synchronizeWithDbEntities(loadedEntities);
-
-		return loadedEntities;
-	}
-
-	public static Collection<ObjEntity> loadObjEntities(DataMap map, DbLoaderConfiguration config,
-			Collection<DbEntity> entities, ObjectNameGenerator nameGenerator) {
-		if (entities.isEmpty()) {
-			return Collections.emptyList();
-		}
-
-		Collection<ObjEntity> loadedEntities = new ArrayList<ObjEntity>(entities.size());
-
-		// doLoad empty ObjEntities for all the tables
-		for (DbEntity dbEntity : entities) {
-
-			// check if there are existing entities
-
-			// TODO: performance. This is an O(n^2) search and it shows on
-			// YourKit profiles. Pre-cache mapped entities perhaps (?)
-			Collection<ObjEntity> existing = map.getMappedEntities(dbEntity);
-			if (!existing.isEmpty()) {
-				loadedEntities.addAll(existing);
-				continue;
-			}
-
-			String objEntityName = DefaultUniqueNameGenerator.generate(NameCheckers.objEntity, map,
-					nameGenerator.createObjEntityName(dbEntity));
-
-			ObjEntity objEntity = new ObjEntity(objEntityName);
-			objEntity.setDbEntity(dbEntity);
-			objEntity.setClassName(config.getGenericClassName() != null ? config.getGenericClassName() : map
-					.getNameWithDefaultPackage(objEntity.getName()));
-
-			map.addObjEntity(objEntity);
-			loadedEntities.add(objEntity);
-		}
-
-		return loadedEntities;
-	}
-
-	/**
-	 * @since 4.0
-	 */
-	protected EntityMergeSupport createEntityMerger(DataMap map) {
-		return new EntityMergeSupport(map, nameGenerator, !creatingMeaningfulPK);
-	}
-
-	protected void loadDbRelationships(DbLoaderConfiguration config, String catalog, String schema,
-			List<DbEntity> tables) throws SQLException {
-		if (config.isSkipRelationshipsLoading()) {
-			return;
-		}
-
-		// Get all the foreign keys referencing this table
-		Map<String, DbEntity> tablesMap = new HashMap<>();
-		for (DbEntity table : tables) {
-			tablesMap.put(table.getName(), table);
-		}
-
-		Map<String, Set<ExportedKey>> keys = loadExportedKeys(config, catalog, schema, tablesMap);
-		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 = tablesMap.get(key.getPKTableName());
-			if (pkEntity == null) {
-				skipRelationLog(key, key.getPKTableName());
-				continue;
-			}
-
-			DbEntity fkEntity = tablesMap.get(key.getFKTableName());
-			if (fkEntity == null) {
-				skipRelationLog(key, key.getFKTableName());
-				continue;
-			}
-
-			if (!new EqualsBuilder().append(pkEntity.getCatalog(), key.pkCatalog)
-					.append(pkEntity.getSchema(), key.pkSchema).append(fkEntity.getCatalog(), key.fkCatalog)
-					.append(fkEntity.getSchema(), key.fkSchema).isEquals()) {
-
-				LOGGER.info("Skip relation: '" + key + "' because it related to objects from other catalog/schema");
-				LOGGER.info("     relation primary key: '" + key.pkCatalog + "." + key.pkSchema + "'");
-				LOGGER.info("       primary key entity: '" + pkEntity.getCatalog() + "." + pkEntity.getSchema() + "'");
-				LOGGER.info("     relation foreign key: '" + key.fkCatalog + "." + key.fkSchema + "'");
-				LOGGER.info("       foreign key entity: '" + fkEntity.getCatalog() + "." + fkEntity.getSchema() + "'");
-				continue;
-			}
-
-			// forwardRelationship is a reference from table with primary key
-			DbRelationship forwardRelationship = new DbRelationship(generateName(pkEntity, key, true));
-			forwardRelationship.setSourceEntity(pkEntity);
-			forwardRelationship.setTargetEntityName(fkEntity);
-
-			// forwardRelationship is a reference from table with foreign key,
-			// it is what exactly we load from db
-			DbRelationshipDetected reverseRelationship = new DbRelationshipDetected(generateName(fkEntity, key, false));
-			reverseRelationship.setFkName(key.getFKName());
-			reverseRelationship.setSourceEntity(fkEntity);
-			reverseRelationship.setTargetEntityName(pkEntity);
-			reverseRelationship.setToMany(false);
-
-			createAndAppendJoins(exportedKeys, pkEntity, fkEntity, forwardRelationship, reverseRelationship);
-
-			boolean toDependentPK = isToDependentPK(forwardRelationship);
-			forwardRelationship.setToDependentPK(toDependentPK);
-
-			boolean isOneToOne = toDependentPK
-					&& fkEntity.getPrimaryKeys().size() == forwardRelationship.getJoins().size();
-
-			forwardRelationship.setToMany(!isOneToOne);
-			forwardRelationship.setName(generateName(pkEntity, key, !isOneToOne));
-
-			if (delegate.dbRelationshipLoaded(fkEntity, reverseRelationship)) {
-				fkEntity.addRelationship(reverseRelationship);
-			}
-			if (delegate.dbRelationshipLoaded(pkEntity, forwardRelationship)) {
-				pkEntity.addRelationship(forwardRelationship);
-			}
-		}
-	}
-
-	private boolean isToDependentPK(DbRelationship forwardRelationship) {
-		for (DbJoin dbJoin : forwardRelationship.getJoins()) {
-			if (!dbJoin.getTarget().isPrimaryKey()) {
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	private void createAndAppendJoins(Set<ExportedKey> exportedKeys, DbEntity pkEntity, DbEntity fkEntity,
-			DbRelationship forwardRelationship, DbRelationshipDetected reverseRelationship) {
-		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;
-			}
-
-			DbAttribute fkAtt = fkEntity.getAttribute(fkName);
-			if (fkAtt == null) {
-				LOGGER.info("no attribute for declared foreign key: " + fkName);
-				continue;
-			}
-
-			forwardRelationship.addJoin(new DbJoin(forwardRelationship, pkName, fkName));
-			reverseRelationship.addJoin(new DbJoin(reverseRelationship, fkName, pkName));
-		}
-	}
-
-	private Map<String, Set<ExportedKey>> loadExportedKeys(DbLoaderConfiguration config, String catalog, String schema,
-			Map<String, DbEntity> tables) throws SQLException {
-		Map<String, Set<ExportedKey>> keys = new HashMap<>();
-
-		for (DbEntity dbEntity : tables.values()) {
-			if (!delegate.dbRelationship(dbEntity)) {
-				continue;
-			}
-
-			ResultSet rs;
-			try {
-				rs = getMetaData().getExportedKeys(catalog, schema, dbEntity.getName());
-			} catch (SQLException cay182Ex) {
-				// Sybase-specific - the line above blows on VIEWS, see CAY-182.
-				LOGGER.info(
-						"Error getting relationships for '" + catalog + "." + schema + "', ignoring. "
-								+ cay182Ex.getMessage(), cay182Ex);
-				return new HashMap<>();
-			}
-
-			try {
-				while (rs.next()) {
-					ExportedKey key = ExportedKey.extractData(rs);
-
-					DbEntity fkEntity = tables.get(key.getFKTableName());
-					if (fkEntity == null) {
-						skipRelationLog(key, key.getFKTableName());
-						continue;
-					}
-
-					if (config.getFiltersConfig().tableFilter(fkEntity.getCatalog(), fkEntity.getSchema())
-							.isIncludeTable(fkEntity.getName()) == null) {
-						continue;
-					}
-
-					Set<ExportedKey> exportedKeys = keys.get(key.getStrKey());
-					if (exportedKeys == null) {
-						exportedKeys = new TreeSet<ExportedKey>();
-
-						keys.put(key.getStrKey(), exportedKeys);
-					}
-					exportedKeys.add(key);
-				}
-
-			} 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) {
-		String forwardPreferredName = nameGenerator.createDbRelationshipName(key, toMany);
-		return DefaultUniqueNameGenerator.generate(NameCheckers.dbRelationship, entity, forwardPreferredName);
-	}
-
-	/**
-	 * Flattens many-to-many relationships in the generated model.
-	 */
-	public static void flattenManyToManyRelationships(DataMap map, Collection<ObjEntity> loadedObjEntities,
-			ObjectNameGenerator objectNameGenerator) {
-		if (loadedObjEntities.isEmpty()) {
-			return;
-		}
-		Collection<ObjEntity> entitiesForDelete = new LinkedList<ObjEntity>();
-
-		for (ObjEntity curEntity : loadedObjEntities) {
-			ManyToManyCandidateEntity entity = ManyToManyCandidateEntity.build(curEntity);
-
-			if (entity != null) {
-				entity.optimizeRelationships(objectNameGenerator);
-				entitiesForDelete.add(curEntity);
-			}
-		}
-
-		// remove needed entities
-		for (ObjEntity curDeleteEntity : entitiesForDelete) {
-			map.removeObjEntity(curDeleteEntity.getName(), true);
-		}
-		loadedObjEntities.removeAll(entitiesForDelete);
-	}
-
-	private void fireObjEntitiesAddedEvents(Collection<ObjEntity> loadedObjEntities) {
-		for (ObjEntity curEntity : loadedObjEntities) {
-			// notify delegate
-			if (delegate != null) {
-				delegate.objEntityAdded(curEntity);
-			}
-		}
-	}
-
-	/**
-	 * By default we want to load Tables and Views for mo types
-	 *
-	 * @see DbLoader#getTableTypes()
-	 * @since 4.0
-	 */
-	public String[] getDefaultTableTypes() {
-		List<String> list = new ArrayList<String>(2);
-
-		String viewType = adapter.tableTypeForView();
-		if (viewType != null) {
-			list.add(viewType);
-		}
-
-		String tableType = adapter.tableTypeForTable();
-		if (tableType != null) {
-			list.add(tableType);
-		}
-
-		return list.toArray(new String[list.size()]);
-	}
-
-	/**
-	 * Performs database reverse engineering and generates DataMap that contains
-	 * default mapping of the tables and views. By default will include regular
-	 * tables and views.
-	 *
-	 * @since 1.0.7
-	 * @deprecated since 4.0 use
-	 *             {@link #load(org.apache.cayenne.map.DataMap, DbLoaderConfiguration)}
-	 *             method that supports catalogs.
-	 */
-	@Deprecated
-	public DataMap loadDataMapFromDB(String schemaPattern, String tablePattern, DataMap dataMap) throws SQLException {
-
-		DbLoaderConfiguration configuration = new DbLoaderConfiguration();
-		configuration.setFiltersConfig(FiltersConfig.create(null, schemaPattern, TableFilter.include(tablePattern),
-				PatternFilter.INCLUDE_NOTHING));
-
-		load(dataMap, configuration);
-		return dataMap;
-	}
-
-	/**
-	 * Performs database reverse engineering and generates DataMap object that
-	 * contains default mapping of the tables and views. Allows to limit types
-	 * of tables to read.
-	 *
-	 * @deprecated since 4.0 use
-	 *             {@link #load(org.apache.cayenne.map.DataMap, DbLoaderConfiguration)}
-	 *             method that supports catalogs.
-	 */
-	@Deprecated
-	public DataMap loadDataMapFromDB(String schemaPattern, String tablePattern, String[] tableTypes, DataMap dataMap)
-			throws SQLException {
-		dataMap.clear();
-
-		DbLoaderConfiguration config = new DbLoaderConfiguration();
-		config.setFiltersConfig(FiltersConfig.create(null, schemaPattern, TableFilter.include(tablePattern),
-				PatternFilter.INCLUDE_NOTHING));
-		config.setTableTypes(tableTypes);
-
-		load(dataMap, config);
-		return dataMap;
-	}
-
-	/**
-	 * Performs database reverse engineering based on the specified config and
-	 * fills the specified DataMap object with DB and object mapping info.
-	 *
-	 * @since 4.0
-	 */
-	public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
-		LOGGER.info("Schema loading...");
-
-		String[] types = config.getTableTypes();
-		if (types == null || types.length == 0) {
-			types = getDefaultTableTypes();
-		}
-
-		for (CatalogFilter catalog : config.getFiltersConfig().catalogs) {
-			for (SchemaFilter schema : catalog.schemas) {
-
-				List<DbEntity> entities = createTableLoader(catalog.name, schema.name, schema.tables).loadDbEntities(
-						dataMap, config, types);
-
-				if (entities != null) {
-					loadDbRelationships(config, catalog.name, schema.name, entities);
-
-					prepareObjLayer(dataMap, config, entities);
-				}
-			}
-		}
-	}
-
-	protected DbTableLoader createTableLoader(String catalog, String schema, TableFilter filter) throws SQLException {
-		return new DbTableLoader(catalog, schema, getMetaData(), delegate, new DbAttributesPerSchemaLoader(catalog,
-				schema, getMetaData(), adapter, filter));
-	}
-
-	public void prepareObjLayer(DataMap dataMap, DbLoaderConfiguration config, Collection<DbEntity> entities) {
-		Collection<ObjEntity> loadedObjEntities = loadObjEntities(dataMap, config, entities);
-		flattenManyToManyRelationships(dataMap, loadedObjEntities, getNameGenerator());
-		fireObjEntitiesAddedEvents(loadedObjEntities);
-	}
-
-	/**
-	 * 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 4.0
-	 */
-	public DataMap load(DbLoaderConfiguration config) throws SQLException {
-
-		DataMap dataMap = new DataMap();
-		load(dataMap, config);
-		loadProcedures(dataMap, config);
-
-		return dataMap;
-	}
-
-	/**
-	 * Loads database stored procedures into the DataMap.
-	 * <p>
-	 * <i>As of 1.1 there is no boolean property or delegate method to make
-	 * procedure loading optional or to implement custom merging logic, so
-	 * currently this method is NOT CALLED from "loadDataMapFromDB" and should
-	 * be invoked explicitly by the user. </i>
-	 * </p>
-	 *
-	 * @since 1.1
-	 * @deprecated since 4.0 use loadProcedures(DataMap, String, String, String)
-	 *             that supports "catalog" pattern.
-	 */
-	@Deprecated
-	public void loadProceduresFromDB(String schemaPattern, String namePattern, DataMap dataMap) throws SQLException {
-		DbLoaderConfiguration configuration = new DbLoaderConfiguration();
-		configuration.setFiltersConfig(FiltersConfig.create(null, schemaPattern, TableFilter.everything(),
-				new PatternFilter().include(namePattern)));
-
-		loadProcedures(dataMap, configuration);
-	}
-
-	/**
-	 * Loads database stored procedures into the DataMap.
-	 * <p>
-	 * <i>As of 1.1 there is no boolean property or delegate method to make
-	 * procedure loading optional or to implement custom merging logic, so
-	 * currently this method is NOT CALLED from "loadDataMapFromDB" and should
-	 * be invoked explicitly by the user. </i>
-	 * </p>
-	 *
-	 * @since 4.0
-	 */
-	public Map<String, Procedure> loadProcedures(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
-
-		Map<String, Procedure> procedures = loadProcedures(config);
-		if (procedures.isEmpty()) {
-			return procedures;
-		}
-
-		loadProceduresColumns(config, procedures);
-
-		for (Procedure procedure : procedures.values()) {
-			dataMap.addProcedure(procedure);
-		}
-
-		return procedures;
-	}
-
-	private void loadProceduresColumns(DbLoaderConfiguration config, Map<String, Procedure> procedures)
-			throws SQLException {
-
-		for (CatalogFilter catalog : config.getFiltersConfig().catalogs) {
-			for (SchemaFilter schema : catalog.schemas) {
-				loadProceduresColumns(procedures, catalog.name, schema.name);
-			}
-		}
-	}
-
-	private void loadProceduresColumns(Map<String, Procedure> procedures, String catalog, String schema)
-			throws SQLException {
-
-		try (ResultSet columnsRS = getMetaData().getProcedureColumns(catalog, schema, null, null);) {
-			while (columnsRS.next()) {
-
-				String s = columnsRS.getString("PROCEDURE_SCHEM");
-				String name = columnsRS.getString("PROCEDURE_NAME");
-				String key = (s == null ? "" : s + '.') + name;
-				Procedure procedure = procedures.get(key);
-				if (procedure == null) {
-					continue;
-				}
-
-				ProcedureParameter column = loadProcedureParams(columnsRS, key, procedure);
-				if (column == null) {
-					continue;
-				}
-				procedure.addCallParameter(column);
-			}
-		}
-	}
-
-	private ProcedureParameter loadProcedureParams(ResultSet columnsRS, String key, Procedure procedure)
-			throws SQLException {
-		String columnName = columnsRS.getString("COLUMN_NAME");
-
-		// 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);
-		}
-
-		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;
-			}
-		}
-
-		int columnType = columnsRS.getInt("DATA_TYPE");
-
-		// ignore precision of non-decimal columns
-		int decimalDigits = -1;
-		if (TypesMapping.isDecimal(columnType)) {
-			decimalDigits = columnsRS.getShort("SCALE");
-			if (columnsRS.wasNull()) {
-				decimalDigits = -1;
-			}
-		}
-
-		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) {
-		switch (type) {
-		case DatabaseMetaData.procedureColumnIn:
-			return ProcedureParameter.IN_PARAMETER;
-		case DatabaseMetaData.procedureColumnInOut:
-			return ProcedureParameter.IN_OUT_PARAMETER;
-		case DatabaseMetaData.procedureColumnOut:
-			return ProcedureParameter.OUT_PARAMETER;
-		default:
-			return -1;
-		}
-	}
-
-	private Map<String, Procedure> loadProcedures(DbLoaderConfiguration config) throws SQLException {
-		Map<String, Procedure> procedures = new HashMap<>();
-
-		FiltersConfig filters = config.getFiltersConfig();
-		for (CatalogFilter catalog : filters.catalogs) {
-			for (SchemaFilter schema : catalog.schemas) {
-				if (filters.proceduresFilter(catalog.name, schema.name).isEmpty()) {
-					continue;
-				}
-
-				procedures.putAll(loadProcedures(filters, catalog.name, schema.name));
-			}
-		}
-
-		return procedures;
-	}
-
-	private Map<String, Procedure> loadProcedures(FiltersConfig filters, String catalog, String schema)
-			throws SQLException {
-		Map<String, Procedure> procedures = new HashMap<>();
-		// get procedures
-
-		try (ResultSet rs = getMetaData().getProcedures(catalog, schema, WILDCARD);) {
-			while (rs.next()) {
-
-				String name = rs.getString("PROCEDURE_NAME");
-				Procedure procedure = new Procedure(name);
-				procedure.setCatalog(rs.getString("PROCEDURE_CAT"));
-				procedure.setSchema(rs.getString("PROCEDURE_SCHEM"));
-
-				if (!filters.proceduresFilter(procedure.getCatalog(), procedure.getSchema()).isInclude(
-						procedure.getName())) {
-					LOGGER.info("skipping Cayenne PK procedure: " + name);
-					continue;
-				}
-
-				switch (rs.getShort("PROCEDURE_TYPE")) {
-				case DatabaseMetaData.procedureNoResult:
-				case DatabaseMetaData.procedureResultUnknown:
-					procedure.setReturningValue(false);
-					break;
-				case DatabaseMetaData.procedureReturnsResult:
-					procedure.setReturningValue(true);
-					break;
-				}
-
-				procedures.put(procedure.getFullyQualifiedName(), procedure);
-			}
-		}
-		return procedures;
-	}
-
-	/**
-	 * Sets new naming strategy for reverse engineering
-	 *
-	 * @since 3.0
-	 */
-	public void setNameGenerator(ObjectNameGenerator strategy) {
-		if (strategy == null) {
-			LOGGER.warn("Attempt to set null into NameGenerator. LegacyNameGenerator will be used.");
-			this.nameGenerator = new LegacyNameGenerator();
-		} else {
-			this.nameGenerator = strategy;
-		}
-	}
-
-	/**
-	 * @return naming strategy for reverse engineering
-	 * @since 3.0
-	 */
-	public ObjectNameGenerator getNameGenerator() {
-		return nameGenerator;
-	}
-}
\ No newline at end of file


[02/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/TokensReversTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/TokensReversTest.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/TokensReversTest.java
deleted file mode 100644
index e13531e..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/TokensReversTest.java
+++ /dev/null
@@ -1,88 +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.merge;
-
-import org.apache.cayenne.dba.hsqldb.HSQLMergerFactory;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.junit.Assert;
-import org.junit.Test;
-
-import java.util.Collections;
-
-import static org.apache.cayenne.merge.builders.ObjectMother.dbAttr;
-import static org.apache.cayenne.merge.builders.ObjectMother.dbEntity;
-
-/**
- * @since 4.0.
- */
-public class TokensReversTest {
-
-    @Test
-    public void testReverses() {
-        DbAttribute attr = dbAttr().build();
-        DbEntity entity = dbEntity().attributes(attr).build();
-        DbRelationship rel = new DbRelationship("rel");
-        rel.setSourceEntity(entity);
-        rel.addJoin(new DbJoin(rel, attr.getName(), "dontKnow"));
-
-        test(factory().createAddColumnToDb(entity, attr));
-        test(factory().createAddColumnToModel(entity, attr));
-        test(factory().createDropColumnToDb(entity, attr));
-        test(factory().createDropColumnToModel(entity, attr));
-
-        test(factory().createAddRelationshipToDb(entity, rel));
-        test(factory().createAddRelationshipToModel(entity, rel));
-        test(factory().createDropRelationshipToDb(entity, rel));
-        test(factory().createDropRelationshipToModel(entity, rel));
-
-        test(factory().createCreateTableToDb(entity));
-        test(factory().createCreateTableToModel(entity));
-        test(factory().createDropTableToDb(entity));
-        test(factory().createDropTableToModel(entity));
-
-        test(factory().createSetAllowNullToDb(entity, attr));
-        test(factory().createSetAllowNullToModel(entity, attr));
-        test(factory().createSetNotNullToDb(entity, attr));
-        test(factory().createSetNotNullToModel(entity, attr));
-
-        DbAttribute attr2 = dbAttr().build();
-        test(factory().createSetColumnTypeToDb(entity, attr, attr2));
-        test(factory().createSetColumnTypeToModel(entity, attr, attr2));
-
-        test(factory().createSetPrimaryKeyToDb(entity, Collections.singleton(attr), Collections.singleton(attr2), "PK"));
-        test(factory().createSetPrimaryKeyToModel(entity, Collections.singleton(attr), Collections.singleton(attr2), "PK"));
-
-        test(factory().createSetValueForNullToDb(entity, attr, new DefaultValueForNullProvider()));
-    }
-
-    private void test(MergerToken token1) {
-        MergerToken token2 = token1.createReverse(factory()).createReverse(factory());
-
-        Assert.assertEquals(token1.getTokenName(), token2.getTokenName());
-        Assert.assertEquals(token1.getTokenValue(), token2.getTokenValue());
-        Assert.assertEquals(token1.getDirection(), token2.getDirection());
-    }
-
-    private MergerFactory factory() {
-        return new HSQLMergerFactory();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/TokensToModelExecution.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/TokensToModelExecution.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/TokensToModelExecution.java
deleted file mode 100644
index 094914b..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/TokensToModelExecution.java
+++ /dev/null
@@ -1,83 +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.merge;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.junit.Test;
-
-import static org.apache.cayenne.merge.builders.ObjectMother.dataMap;
-import static org.apache.cayenne.merge.builders.ObjectMother.dbAttr;
-import static org.apache.cayenne.merge.builders.ObjectMother.dbEntity;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * @since 4.0.
- */
-public class TokensToModelExecution {
-
-    @Test
-    public void testCreateAndDropTable() throws Exception {
-        DbEntity entity = dbEntity().build();
-
-        DataMap dataMap = dataMap().build();
-        assertTrue(dataMap.getDbEntityMap().isEmpty());
-        assertTrue(dataMap.getObjEntityMap().isEmpty());
-
-        MergerContext context = MergerContext.builder(dataMap).dataNode(new DataNode()).build();
-        factory().createCreateTableToModel(entity).execute(context);
-
-        assertEquals(1, dataMap.getDbEntityMap().size());
-        assertEquals(1, dataMap.getObjEntities().size());
-        assertEquals(entity, dataMap.getDbEntity(entity.getName()));
-
-        factory().createDropTableToModel(entity).execute(context);
-        assertTrue(dataMap.getDbEntityMap().isEmpty());
-        assertTrue(dataMap.getObjEntityMap().isEmpty());
-    }
-
-    @Test
-    public void testCreateAndDropColumn() throws Exception {
-        DbAttribute attr = dbAttr("attr").build();
-        DbEntity entity = dbEntity().build();
-
-        DataMap dataMap = dataMap().with(entity).build();
-        assertEquals(1, dataMap.getDbEntityMap().size());
-        assertTrue(dataMap.getObjEntityMap().isEmpty());
-
-        MergerContext context = MergerContext.builder(dataMap).dataNode(new DataNode()).build();
-        factory().createAddColumnToModel(entity, attr).execute(context);
-
-        assertEquals(1, dataMap.getDbEntityMap().size());
-        assertEquals(1, entity.getAttributes().size());
-        assertEquals(attr, entity.getAttribute(attr.getName()));
-
-        factory().createDropColumnToModel(entity, attr).execute(context);
-        assertEquals(1, dataMap.getDbEntityMap().size());
-        assertTrue(entity.getAttributes().isEmpty());
-        assertTrue(dataMap.getObjEntityMap().isEmpty());
-    }
-
-    private MergerFactory factory() {
-        return new MergerFactory();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/ValueForNullIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/ValueForNullIT.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/ValueForNullIT.java
deleted file mode 100644
index fb43eb3..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/ValueForNullIT.java
+++ /dev/null
@@ -1,128 +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.merge;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.sql.Types;
-import java.util.List;
-
-import junit.framework.AssertionFailedError;
-
-import org.apache.cayenne.DataObject;
-import org.apache.cayenne.Persistent;
-import org.apache.cayenne.access.DataContext;
-import org.apache.cayenne.access.jdbc.SQLParameterBinding;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.query.SelectQuery;
-import org.junit.Test;
-
-public class ValueForNullIT extends MergeCase {
-
-	private static final String DEFAULT_VALUE_STRING = "DEFSTRING";
-
-	@Inject
-	private DataContext context;
-
-	@Test
-	public void test() throws Exception {
-		DbEntity dbEntity = map.getDbEntity("PAINTING");
-		assertNotNull(dbEntity);
-		ObjEntity objEntity = map.getObjEntity("Painting");
-		assertNotNull(objEntity);
-
-		// insert some rows before adding "not null" column
-		final int nrows = 10;
-		for (int i = 0; i < nrows; i++) {
-			DataObject o = (DataObject) context.newObject("Painting");
-			o.writeProperty("paintingTitle", "ptitle" + i);
-		}
-		context.commitChanges();
-
-		// create and add new column to model and db
-		DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
-
-		column.setMandatory(false);
-		column.setMaxLength(10);
-		dbEntity.addAttribute(column);
-		assertTrue(dbEntity.getAttributes().contains(column));
-		assertEquals(column, dbEntity.getAttribute(column.getName()));
-		assertTokensAndExecute(1, 0);
-
-		// need obj attr to be able to query
-		ObjAttribute objAttr = new ObjAttribute("newcol2");
-		objAttr.setDbAttributePath(column.getName());
-		objEntity.addAttribute(objAttr);
-
-		// check that is was merged
-		assertTokensAndExecute(0, 0);
-
-		// set not null
-		column.setMandatory(true);
-
-		// merge to db
-		assertTokensAndExecute(2, 0);
-
-		// check that is was merged
-		assertTokensAndExecute(0, 0);
-
-		// check values for null
-		Expression qual = ExpressionFactory.matchExp(objAttr.getName(), DEFAULT_VALUE_STRING);
-		SelectQuery query = new SelectQuery("Painting", qual);
-		List<Persistent> rows = context.performQuery(query);
-		assertEquals(nrows, rows.size());
-
-		// clean up
-		dbEntity.removeAttribute(column.getName());
-		assertTokensAndExecute(1, 0);
-		assertTokensAndExecute(0, 0);
-	}
-
-	@Override
-	protected DbMerger createMerger(MergerFactory mergerFactory, final ValueForNullProvider valueForNullProvider) {
-		return super.createMerger(mergerFactory, new DefaultValueForNullProvider() {
-
-			@Override
-			protected SQLParameterBinding get(DbEntity entity, DbAttribute column) {
-				int type = column.getType();
-				switch (type) {
-				case Types.VARCHAR:
-					return new SQLParameterBinding(DEFAULT_VALUE_STRING, type, -1);
-				default:
-					throw new AssertionFailedError("should not get here");
-				}
-			}
-
-			@Override
-			public boolean hasValueFor(DbEntity entity, DbAttribute column) {
-				return true;
-			}
-
-		});
-	}
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/Builder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/Builder.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/Builder.java
deleted file mode 100644
index fd379c7..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/Builder.java
+++ /dev/null
@@ -1,38 +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.merge.builders;
-
-/**
- * Base interface for all domain builders
- *
- * @since 4.0.
- */
-public interface Builder<T> {
-
-    /**
-     * Build valid object. If some required data omitted it will be filled with random data.
-     * */
-    T build();
-
-    /**
-     * Build valid object and add some optional fields randomly.
-     * */
-    T random();
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DataMapBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DataMapBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DataMapBuilder.java
deleted file mode 100644
index 662ba2a..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DataMapBuilder.java
+++ /dev/null
@@ -1,128 +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.merge.builders;
-
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.ObjEntity;
-
-import java.util.Collections;
-
-/**
- * @since 4.0.
- */
-public class DataMapBuilder extends DefaultBuilder<DataMap> {
-
-    public DataMapBuilder() {
-        this(new DataMap());
-    }
-
-    public DataMapBuilder(DataMap dataMap) {
-        super(dataMap);
-    }
-
-    public DataMapBuilder with(DbEntity ... entities) {
-        for (DbEntity entity : entities) {
-            obj.addDbEntity(entity);
-        }
-
-        return this;
-    }
-
-    public DataMapBuilder with(DbEntityBuilder ... entities) {
-        for (DbEntityBuilder entity : entities) {
-            obj.addDbEntity(entity.build());
-        }
-
-        return this;
-    }
-
-    public DataMapBuilder withDbEntities(int count) {
-        for (int i = 0; i < count; i++) {
-            obj.addDbEntity(ObjectMother.dbEntity().random());
-        }
-
-        return this;
-    }
-
-    public DataMapBuilder with(ObjEntity... entities) {
-        for (ObjEntity entity : entities) {
-            obj.addObjEntity(entity);
-        }
-
-        return this;
-    }
-
-    public DataMapBuilder with(ObjEntityBuilder ... entities) {
-        for (ObjEntityBuilder entity : entities) {
-            obj.addObjEntity(entity.build());
-        }
-
-        return this;
-    }
-
-    public DataMapBuilder withObjEntities(int count) {
-        for (int i = 0; i < count; i++) {
-            obj.addObjEntity(ObjectMother.objEntity().random());
-        }
-
-        return this;
-    }
-
-    public DataMapBuilder join(String from, String to) {
-        return join(null, from, to);
-    }
-
-    public DataMapBuilder join(String name, String from, String to) {
-        String[] fromSplit = from.split("\\.");
-        DbEntity fromEntity = obj.getDbEntity(fromSplit[0]);
-        if (fromEntity == null) {
-            throw new IllegalArgumentException("Entity '" + fromSplit[0] + "' is undefined");
-        }
-
-        String[] toSplit = to.split("\\.");
-
-        fromEntity.addRelationship(new DbRelationshipBuilder(name)
-                .from(fromEntity, fromSplit[1])
-                .to(toSplit[0], toSplit[1])
-
-                .build());
-
-        return this;
-    }
-
-    public DataMap build() {
-        if (obj.getNamespace() == null) {
-            obj.setNamespace(new EntityResolver(Collections.singleton(obj)));
-        }
-
-        return obj;
-    }
-
-    @Override
-    public DataMap random() {
-        if (dataFactory.chance(90)) {
-            withDbEntities(dataFactory.getNumberUpTo(10));
-        }
-
-
-        return build();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbAttributeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbAttributeBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbAttributeBuilder.java
deleted file mode 100644
index 1682b50..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbAttributeBuilder.java
+++ /dev/null
@@ -1,115 +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.merge.builders;
-
-import org.apache.cayenne.datafactory.DictionaryValueProvider;
-import org.apache.cayenne.datafactory.ValueProvider;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.map.DbAttribute;
-
-import static org.apache.commons.lang.StringUtils.isEmpty;
-
-/**
- * @since 4.0.
- */
-public class DbAttributeBuilder extends DefaultBuilder<DbAttribute> {
-
-    private static final ValueProvider<String> TYPES_RANDOM = new DictionaryValueProvider<String>(ValueProvider.RANDOM) {
-        @Override
-        protected String[] values() {
-            return TypesMapping.getDatabaseTypes();
-        }
-    };
-
-    public DbAttributeBuilder() {
-        super(new DbAttribute());
-    }
-
-    public DbAttributeBuilder name() {
-        return name(getRandomJavaName());
-    }
-
-    public DbAttributeBuilder name(String name) {
-        obj.setName(name);
-
-        return this;
-    }
-
-    public DbAttributeBuilder type() {
-        return type(TYPES_RANDOM.randomValue());
-    }
-
-    public DbAttributeBuilder type(String item) {
-        obj.setType(TypesMapping.getSqlTypeByName(item));
-
-        return this;
-    }
-
-    public DbAttributeBuilder typeInt() {
-        return type(TypesMapping.SQL_INTEGER);
-    }
-
-    public DbAttributeBuilder typeBigInt() {
-        return type(TypesMapping.SQL_BIGINT);
-    }
-
-    public DbAttributeBuilder typeVarchar(int length) {
-        type(TypesMapping.SQL_VARCHAR);
-        length(length);
-
-        return this;
-    }
-
-    private DbAttributeBuilder length(int length) {
-        obj.setMaxLength(length);
-
-        return this;
-    }
-
-    public DbAttributeBuilder primaryKey() {
-        obj.setPrimaryKey(true);
-
-        return this;
-    }
-
-    public DbAttributeBuilder mandatory() {
-        obj.setMandatory(true);
-
-        return this;
-    }
-
-    @Override
-    public DbAttribute build() {
-        if (isEmpty(obj.getName())) {
-            name();
-        }
-
-        if (obj.getType() == TypesMapping.NOT_DEFINED) {
-            type();
-        }
-
-        return obj;
-    }
-
-    @Override
-    public DbAttribute random() {
-        return build();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbEntityBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbEntityBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbEntityBuilder.java
deleted file mode 100644
index 9cbd456..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbEntityBuilder.java
+++ /dev/null
@@ -1,90 +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.merge.builders;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.commons.lang.StringUtils;
-
-/**
- * @since 4.0.
- */
-public class DbEntityBuilder extends DefaultBuilder<DbEntity> {
-
-    public DbEntityBuilder() {
-        super(new DbEntity());
-    }
-
-    public DbEntityBuilder name() {
-        return name(getRandomJavaName());
-    }
-
-    public DbEntityBuilder name(String name) {
-        obj.setName(name);
-
-        return this;
-    }
-
-    public DbEntityBuilder attributes(DbAttribute ... attributes) {
-        for (DbAttribute attribute : attributes) {
-            obj.addAttribute(attribute);
-        }
-
-        return this;
-    }
-
-    public DbEntityBuilder attributes(DbAttributeBuilder ... attributes) {
-        for (DbAttributeBuilder attribute : attributes) {
-            obj.addAttribute(attribute.build());
-        }
-
-        return this;
-    }
-
-    public DbEntityBuilder attributes(int numberUpTo) {
-        for (int i = 0; i < numberUpTo; i++) {
-            try {
-                obj.addAttribute(new DbAttributeBuilder().random());
-            } catch (IllegalArgumentException e) {
-                i--; // try again
-            }
-        }
-
-        return this;
-    }
-
-
-    @Override
-    public DbEntity build() {
-        if (obj.getName() == null) {
-            obj.setName(StringUtils.capitalize(getRandomJavaName()));
-        }
-
-        return obj;
-    }
-
-    @Override
-    public DbEntity random() {
-        if (dataFactory.chance(99)) {
-            attributes(dataFactory.getNumberUpTo(20));
-        }
-
-        return build();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbRelationshipBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbRelationshipBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbRelationshipBuilder.java
deleted file mode 100644
index 2bf5dba..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DbRelationshipBuilder.java
+++ /dev/null
@@ -1,85 +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.merge.builders;
-
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-
-/**
- * @since 4.0.
- */
-public class DbRelationshipBuilder extends DefaultBuilder<DbRelationship> {
-
-    private String[] from;
-    private String[] to;
-
-    public DbRelationshipBuilder() {
-        super(new DbRelationship());
-    }
-
-    public DbRelationshipBuilder(String name) {
-        super(new DbRelationship(name));
-    }
-
-    public DbRelationshipBuilder(DbRelationship obj) {
-        super(obj);
-    }
-
-    public DbRelationshipBuilder name() {
-        return name(getRandomJavaName());
-    }
-
-    public DbRelationshipBuilder name(String name) {
-        obj.setName(name);
-
-        return this;
-    }
-
-    public DbRelationshipBuilder from(DbEntity entity, String ... columns) {
-        obj.setSourceEntity(entity);
-        this.from = columns;
-
-        return this;
-    }
-
-    public DbRelationshipBuilder to(String entityName, String ... columns) {
-        obj.setTargetEntityName(entityName);
-        this.to = columns;
-
-        return this;
-    }
-
-    @Override
-    public DbRelationship build() {
-        if (obj.getName() == null) {
-            name();
-        }
-
-        if (from.length != to.length) {
-            throw new IllegalStateException("from and to columns name size mismatch");
-        }
-
-        for (int i = 0; i < from.length; i++) {
-            obj.addJoin(new DbJoin(obj, from[i], to[i]));
-        }
-
-        return obj;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DefaultBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DefaultBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DefaultBuilder.java
deleted file mode 100644
index 5e9cea2..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/DefaultBuilder.java
+++ /dev/null
@@ -1,57 +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.merge.builders;
-
-import org.apache.cayenne.datafactory.DataFactory;
-import org.apache.commons.lang.StringUtils;
-
-/**
- * @since 4.0.
- */
-public abstract class DefaultBuilder<T> implements Builder<T> {
-
-    protected final DataFactory dataFactory;
-    protected final T obj;
-
-
-    protected DefaultBuilder(T obj) {
-        this.dataFactory = new DataFactory();
-        this.obj = obj;
-    }
-
-    public String getRandomJavaName() {
-        int count = dataFactory.getNumberBetween(1, 5);
-        StringBuilder res = new StringBuilder();
-        for (int i = 0; i < count; i++) {
-            res.append(StringUtils.capitalize(dataFactory.getRandomWord()));
-        }
-
-        return StringUtils.uncapitalize(res.toString());
-    }
-
-    @Override
-    public T build() {
-        return obj;
-    }
-
-    @Override
-    public T random() {
-        return build();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjAttributeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjAttributeBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjAttributeBuilder.java
deleted file mode 100644
index 6a74936..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjAttributeBuilder.java
+++ /dev/null
@@ -1,67 +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.merge.builders;
-
-import org.apache.cayenne.map.ObjAttribute;
-
-/**
- * @since 4.0.
- */
-public class ObjAttributeBuilder extends DefaultBuilder<ObjAttribute> {
-
-    public ObjAttributeBuilder() {
-        super(new ObjAttribute());
-    }
-
-    public ObjAttributeBuilder name() {
-        return name(getRandomJavaName());
-    }
-
-    public ObjAttributeBuilder name(String name) {
-        obj.setName(name);
-
-        return this;
-    }
-
-    public ObjAttributeBuilder type(Class type) {
-        obj.setType(type.getCanonicalName());
-
-        return this;
-    }
-
-    public ObjAttributeBuilder dbPath(String path) {
-        obj.setDbAttributePath(path);
-
-        return this;
-    }
-
-    @Override
-    public ObjAttribute build() {
-        if (obj.getName() == null) {
-            name();
-        }
-
-        return obj;
-    }
-
-    @Override
-    public ObjAttribute random() {
-        return build();
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjEntityBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjEntityBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjEntityBuilder.java
deleted file mode 100644
index dc78b29..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjEntityBuilder.java
+++ /dev/null
@@ -1,98 +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.merge.builders;
-
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.commons.lang.StringUtils;
-
-/**
- * @since 4.0.
- */
-public class ObjEntityBuilder extends DefaultBuilder<ObjEntity> {
-
-    public ObjEntityBuilder() {
-        super(new ObjEntity());
-    }
-
-    public ObjEntityBuilder name() {
-        return name(getRandomJavaName());
-    }
-
-    public ObjEntityBuilder name(String name) {
-        obj.setName(name);
-
-        return this;
-    }
-
-    public ObjEntityBuilder attributes(ObjAttribute... attributes) {
-        for (ObjAttribute attribute : attributes) {
-            obj.addAttribute(attribute);
-        }
-
-        return this;
-    }
-
-    public ObjEntityBuilder attributes(ObjAttributeBuilder ... attributes) {
-        for (ObjAttributeBuilder attribute : attributes) {
-            obj.addAttribute(attribute.build());
-        }
-
-        return this;
-    }
-
-    public ObjEntityBuilder attributes(int numberUpTo) {
-        for (int i = 0; i < numberUpTo; i++) {
-            obj.addAttribute(new ObjAttributeBuilder().random());
-        }
-
-        return this;
-    }
-
-
-    @Override
-    public ObjEntity build() {
-        if (obj.getName() == null) {
-            obj.setName(StringUtils.capitalize(getRandomJavaName()));
-        }
-
-        return obj;
-    }
-
-    @Override
-    public ObjEntity random() {
-        if (dataFactory.chance(99)) {
-            attributes(dataFactory.getNumberUpTo(20));
-        }
-
-        return build();
-    }
-
-    public ObjEntityBuilder clazz(String s) {
-        obj.setClassName(s);
-
-        return this;
-    }
-
-    public ObjEntityBuilder dbEntity(String table) {
-        obj.setDbEntityName(table);
-
-        return this;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjectMother.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjectMother.java b/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjectMother.java
deleted file mode 100644
index 76ed761..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/merge/builders/ObjectMother.java
+++ /dev/null
@@ -1,70 +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.merge.builders;
-
-import org.apache.cayenne.map.DataMap;
-
-/**
- * Factory for test data see pattern definition:
- * http://martinfowler.com/bliki/ObjectMother.html
- *
- * @since 4.0.
- */
-public class ObjectMother {
-
-    public static DataMapBuilder dataMap() {
-        return new DataMapBuilder();
-    }
-
-    public static DataMapBuilder dataMap(DataMap dataMap) {
-        return new DataMapBuilder(dataMap);
-    }
-
-    public static DbEntityBuilder dbEntity() {
-        return new DbEntityBuilder();
-    }
-
-    public static DbEntityBuilder dbEntity(String name) {
-        return new DbEntityBuilder().name(name);
-    }
-
-    public static ObjEntityBuilder objEntity() {
-        return new ObjEntityBuilder();
-    }
-
-    public static ObjEntityBuilder objEntity(String packageName, String className, String table) {
-        return new ObjEntityBuilder()
-                .name(className)
-                .clazz(packageName + "." + className)
-                .dbEntity(table);
-    }
-
-    public static ObjAttributeBuilder objAttr(String name) {
-        return new ObjAttributeBuilder().name(name);
-    }
-
-    public static DbAttributeBuilder dbAttr(String name) {
-        return dbAttr().name(name);
-    }
-
-    public static DbAttributeBuilder dbAttr() {
-        return new DbAttributeBuilder();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerRuntimeProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerRuntimeProvider.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerRuntimeProvider.java
index 29820e0..79c02cf 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerRuntimeProvider.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/ServerRuntimeProvider.java
@@ -29,6 +29,9 @@ import org.apache.cayenne.di.Module;
 import org.apache.cayenne.di.Provider;
 import org.apache.cayenne.unit.UnitDbAdapter;
 
+import java.util.Collection;
+import java.util.Collections;
+
 public class ServerRuntimeProvider implements Provider<ServerRuntime> {
 
     private ServerCaseProperties properties;
@@ -48,6 +51,7 @@ public class ServerRuntimeProvider implements Provider<ServerRuntime> {
         this.unitDbAdapter = unitDbAdapter;
     }
 
+    @Override
     public ServerRuntime get() throws ConfigurationException {
 
         String configurationLocation = properties.getConfigurationLocation();
@@ -56,11 +60,18 @@ public class ServerRuntimeProvider implements Provider<ServerRuntime> {
                     + "annotate your test case with @UseServerRuntime");
         }
 
-        return new ServerRuntime(configurationLocation, new ServerExtraModule());
+        Collection<? extends Module> modules = getExtraModules();
+
+        return new ServerRuntime(configurationLocation, modules.toArray(new Module[modules.size()]));
+    }
+
+    protected Collection<? extends Module> getExtraModules() {
+        return Collections.singleton(new ServerExtraModule());
     }
 
     class ServerExtraModule implements Module {
 
+        @Override
         public void configure(Binder binder) {
 
             // these are the objects overriding standard ServerModule definitions or

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/util/EntityMergeSupportIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/util/EntityMergeSupportIT.java b/cayenne-server/src/test/java/org/apache/cayenne/util/EntityMergeSupportIT.java
deleted file mode 100644
index e3f1bc3..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/util/EntityMergeSupportIT.java
+++ /dev/null
@@ -1,103 +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.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.sql.Types;
-import java.util.Arrays;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.DeleteRule;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.merge.MergeCase;
-import org.junit.Test;
-
-public class EntityMergeSupportIT extends MergeCase {
-
-	@Test
-	public void testMerging() {
-		DbEntity dbEntity1 = new DbEntity("NEW_TABLE");
-
-		DbAttribute e1col1 = new DbAttribute("ID", Types.INTEGER, dbEntity1);
-		e1col1.setMandatory(true);
-		e1col1.setPrimaryKey(true);
-		dbEntity1.addAttribute(e1col1);
-
-		DbAttribute e1col2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity1);
-		e1col2.setMaxLength(10);
-		e1col2.setMandatory(false);
-		dbEntity1.addAttribute(e1col2);
-
-		map.addDbEntity(dbEntity1);
-
-		DbEntity dbEntity2 = new DbEntity("NEW_TABLE2");
-		DbAttribute e2col1 = new DbAttribute("ID", Types.INTEGER, dbEntity2);
-		e2col1.setMandatory(true);
-		e2col1.setPrimaryKey(true);
-		dbEntity2.addAttribute(e2col1);
-		DbAttribute e2col2 = new DbAttribute("FK", Types.INTEGER, dbEntity2);
-		dbEntity2.addAttribute(e2col2);
-
-		map.addDbEntity(dbEntity2);
-
-		// create db relationships
-		DbRelationship rel1To2 = new DbRelationship("rel1To2");
-		rel1To2.setSourceEntity(dbEntity1);
-		rel1To2.setTargetEntityName(dbEntity2);
-		rel1To2.setToMany(true);
-		rel1To2.addJoin(new DbJoin(rel1To2, e1col1.getName(), e2col2.getName()));
-		dbEntity1.addRelationship(rel1To2);
-		DbRelationship rel2To1 = new DbRelationship("rel2To1");
-		rel2To1.setSourceEntity(dbEntity2);
-		rel2To1.setTargetEntityName(dbEntity1);
-		rel2To1.setToMany(false);
-		rel2To1.addJoin(new DbJoin(rel2To1, e2col2.getName(), e1col1.getName()));
-		dbEntity2.addRelationship(rel2To1);
-		assertSame(rel1To2, rel2To1.getReverseRelationship());
-		assertSame(rel2To1, rel1To2.getReverseRelationship());
-
-		ObjEntity objEntity1 = new ObjEntity("NewTable");
-		objEntity1.setDbEntity(dbEntity1);
-		map.addObjEntity(objEntity1);
-
-		ObjEntity objEntity2 = new ObjEntity("NewTable2");
-		objEntity2.setDbEntity(dbEntity2);
-		map.addObjEntity(objEntity2);
-
-		assertTrue(new EntityMergeSupport(map).synchronizeWithDbEntities(Arrays.asList(objEntity1, objEntity2)));
-		assertNotNull(objEntity1.getAttribute("name"));
-		assertNotNull(objEntity1.getRelationship("rel1To2"));
-		assertNotNull(objEntity2.getRelationship("rel2To1"));
-
-		assertEquals(objEntity1.getRelationship("rel1To2").getDeleteRule(), DeleteRule.DEFAULT_DELETE_RULE_TO_MANY);
-		assertEquals(objEntity2.getRelationship("rel2To1").getDeleteRule(), DeleteRule.DEFAULT_DELETE_RULE_TO_ONE);
-
-		map.removeObjEntity(objEntity2.getName());
-		map.removeObjEntity(objEntity1.getName());
-		map.removeDbEntity(dbEntity2.getName());
-		map.removeDbEntity(dbEntity1.getName());
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index d4fea49..0000000
--- a/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/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/2f7b1d53/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
deleted file mode 100644
index e68645f..0000000
--- a/cayenne-server/src/test/resources/org/apache/cayenne/access/loader/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/2f7b1d53/cayenne-tools/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-tools/pom.xml b/cayenne-tools/pom.xml
index 581c7df..15816c5 100644
--- a/cayenne-tools/pom.xml
+++ b/cayenne-tools/pom.xml
@@ -42,6 +42,13 @@
             <scope>test</scope>
             <type>test-jar</type>
         </dependency>
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-dbsync</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
 
         <dependency>
 			<groupId>org.apache.cayenne.build-tools</groupId>
@@ -94,6 +101,13 @@
 			<version>${project.version}</version>
 		</dependency>
 
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-dbsync</artifactId>
+            <version>${project.version}</version>
+            <scope>compile</scope>
+        </dependency>
+
 		<dependency>
 			<groupId>foundrylogic.vpp</groupId>
 			<artifactId>vpp</artifactId>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java b/cayenne-tools/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
index f7471a6..e8b6b76 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/gen/ClassGenerationAction.java
@@ -18,18 +18,8 @@
  ****************************************************************/
 package org.apache.cayenne.gen;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.loader.NamePatternMatcher;
+import org.apache.cayenne.dbsync.reverse.NamePatternMatcher;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.Embeddable;
 import org.apache.cayenne.map.ObjEntity;
@@ -41,6 +31,16 @@ import org.apache.velocity.app.VelocityEngine;
 import org.apache.velocity.runtime.RuntimeConstants;
 import org.apache.velocity.runtime.log.NullLogSystem;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
 public class ClassGenerationAction {
 	static final String TEMPLATES_DIR_NAME = "templates/v1_2/";
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/AntDataPortDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/AntDataPortDelegate.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/AntDataPortDelegate.java
index 828d744..3e98829 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/AntDataPortDelegate.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/AntDataPortDelegate.java
@@ -19,19 +19,19 @@
 
 package org.apache.cayenne.tools;
 
-import java.util.Iterator;
-import java.util.List;
-import java.util.regex.Pattern;
-
 import org.apache.cayenne.access.DataPort;
 import org.apache.cayenne.access.DataPortDelegate;
-import org.apache.cayenne.access.loader.NamePatternMatcher;
+import org.apache.cayenne.dbsync.reverse.NamePatternMatcher;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.query.Query;
 import org.apache.tools.ant.Project;
 import org.apache.tools.ant.Task;
 
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+
 /**
  * DataPortDelegate implementation that works in the context of Ant DataPortTask
  * task execution, performing entity filtering and logging functions.

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorEntityFilterAction.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorEntityFilterAction.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorEntityFilterAction.java
index 49329a7..08a39df 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorEntityFilterAction.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorEntityFilterAction.java
@@ -18,16 +18,16 @@
  ****************************************************************/
 package org.apache.cayenne.tools;
 
+import org.apache.cayenne.dbsync.reverse.NameFilter;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.Embeddable;
+import org.apache.cayenne.map.ObjEntity;
+
 import java.net.MalformedURLException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 
-import org.apache.cayenne.access.loader.NameFilter;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.Embeddable;
-import org.apache.cayenne.map.ObjEntity;
-
 /**
  * Performs entity filtering to build a collection of entities that should be used in
  * class generation.

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorTask.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorTask.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorTask.java
index 6ed29cb..6edcd6c 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorTask.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/CayenneGeneratorTask.java
@@ -19,7 +19,7 @@
 package org.apache.cayenne.tools;
 
 import foundrylogic.vpp.VPPConfig;
-import org.apache.cayenne.access.loader.NamePatternMatcher;
+import org.apache.cayenne.dbsync.reverse.NamePatternMatcher;
 import org.apache.cayenne.gen.ArtifactsGenerationMode;
 import org.apache.cayenne.gen.ClassGenerationAction;
 import org.apache.cayenne.gen.ClientClassGenerationAction;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java
index 5e0ab99..faa3653 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbGeneratorTask.java
@@ -19,12 +19,10 @@
 
 package org.apache.cayenne.tools;
 
-import java.sql.Driver;
-import java.util.Collections;
-
 import org.apache.cayenne.access.DbGenerator;
 import org.apache.cayenne.datasource.DriverDataSource;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
 import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.log.NoopJdbcEventLogger;
@@ -36,6 +34,9 @@ import org.apache.commons.logging.Log;
 import org.apache.tools.ant.BuildException;
 import org.apache.tools.ant.Project;
 
+import java.sql.Driver;
+import java.util.Collections;
+
 /**
  * An Ant Task that is a frontend to Cayenne DbGenerator allowing schema
  * generation from DataMap using Ant.
@@ -68,7 +69,7 @@ public class DbGeneratorTask extends CayenneTask {
 		validateAttributes();
 
 		ClassLoader loader = null;
-		Injector injector = DIBootstrap.createInjector(new ToolsModule(logger));
+		Injector injector = DIBootstrap.createInjector(new CayenneDbSyncModule(), new ToolsModule(logger));
 		try {
 			loader = Thread.currentThread().getContextClassLoader();
 			Thread.currentThread().setContextClassLoader(DbGeneratorTask.class.getClassLoader());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbImporterTask.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbImporterTask.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbImporterTask.java
index e4fa545..f1797f1 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbImporterTask.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/DbImporterTask.java
@@ -18,7 +18,6 @@
  ****************************************************************/
 package org.apache.cayenne.tools;
 
-import org.apache.cayenne.access.loader.filters.LegacyFilterConfigBridge;
 import org.apache.cayenne.configuration.ConfigurationNameMapper;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.XMLDataMapLoader;
@@ -32,12 +31,14 @@ import org.apache.cayenne.dbimport.DefaultReverseEngineeringLoader;
 import org.apache.cayenne.dbimport.ExcludeColumn;
 import org.apache.cayenne.dbimport.ExcludeProcedure;
 import org.apache.cayenne.dbimport.ExcludeTable;
-import org.apache.cayenne.dbimport.FiltersConfigBuilder;
 import org.apache.cayenne.dbimport.IncludeColumn;
 import org.apache.cayenne.dbimport.IncludeProcedure;
 import org.apache.cayenne.dbimport.IncludeTable;
 import org.apache.cayenne.dbimport.ReverseEngineering;
 import org.apache.cayenne.dbimport.Schema;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
+import org.apache.cayenne.dbsync.reverse.FiltersConfigBuilder;
+import org.apache.cayenne.dbsync.reverse.filters.LegacyFilterConfigBridge;
 import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.map.DataMap;
@@ -91,7 +92,7 @@ public class DbImporterTask extends Task {
         config.setTableTypes(reverseEngineering.getTableTypes());
 
         if (isReverseEngineeringDefined) {
-            Injector injector = DIBootstrap.createInjector(new ToolsModule(logger), new DbImportModule());
+            Injector injector = DIBootstrap.createInjector(new CayenneDbSyncModule(), new ToolsModule(logger), new DbImportModule());
 
             validateDbImportConfiguration(config, injector);
 
@@ -120,7 +121,7 @@ public class DbImporterTask extends Task {
                     XMLDataMapLoader xmlDataMapLoader = new XMLDataMapLoader();
                     DataMap dataMap = xmlDataMapLoader.load(resource);
                     if (dataMap.getReverseEngineering() != null) {
-                        Injector injector = DIBootstrap.createInjector(new ToolsModule(logger), new DbImportModule());
+                        Injector injector = DIBootstrap.createInjector(new CayenneDbSyncModule(), new ToolsModule(logger), new DbImportModule());
                         try {
                             ConfigurationNameMapper nameMapper = injector.getInstance(ConfigurationNameMapper.class);
                             String reverseEngineeringLocation = nameMapper.configurationLocation(ReverseEngineering.class, dataMap.getReverseEngineering().getName());

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java
index 0914fb5..e69905a 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportConfiguration.java
@@ -19,28 +19,28 @@
 package org.apache.cayenne.tools.dbimport;
 
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.DbLoader;
-import org.apache.cayenne.access.loader.DbLoaderConfiguration;
-import org.apache.cayenne.access.DbLoaderDelegate;
-import org.apache.cayenne.access.loader.DefaultDbLoaderDelegate;
-import org.apache.cayenne.access.loader.LoggingDbLoaderDelegate;
-import org.apache.cayenne.access.loader.NameFilter;
-import org.apache.cayenne.access.loader.filters.CatalogFilter;
-import org.apache.cayenne.access.loader.filters.FiltersConfig;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.conn.DataSourceInfo;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.DbMergerConfig;
+import org.apache.cayenne.dbsync.merge.DefaultModelMergeDelegate;
+import org.apache.cayenne.dbsync.merge.EntityMergeSupport;
+import org.apache.cayenne.dbsync.merge.ModelMergeDelegate;
+import org.apache.cayenne.dbsync.reverse.DbLoader;
+import org.apache.cayenne.dbsync.reverse.DbLoaderConfiguration;
+import org.apache.cayenne.dbsync.reverse.DbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.DefaultDbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.LoggingDbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.NameFilter;
+import org.apache.cayenne.dbsync.reverse.NamePatternMatcher;
+import org.apache.cayenne.dbsync.reverse.filters.CatalogFilter;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.naming.LegacyNameGenerator;
 import org.apache.cayenne.map.naming.ObjectNameGenerator;
-import org.apache.cayenne.merge.DbMergerConfig;
-import org.apache.cayenne.merge.DefaultModelMergeDelegate;
-import org.apache.cayenne.merge.ModelMergeDelegate;
 import org.apache.cayenne.resource.URLResource;
-import org.apache.cayenne.access.loader.NamePatternMatcher;
-import org.apache.cayenne.util.EntityMergeSupport;
 import org.apache.commons.logging.Log;
 
 import java.io.File;
@@ -48,7 +48,6 @@ import java.io.IOException;
 import java.net.MalformedURLException;
 import java.sql.Connection;
 import java.util.Collections;
-import java.util.List;
 
 import static org.apache.commons.lang.StringUtils.isNotEmpty;
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java
index 7772b73..bc6abab 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportDbLoaderDelegate.java
@@ -19,7 +19,7 @@
 
 package org.apache.cayenne.tools.dbimport;
 
-import org.apache.cayenne.access.loader.DefaultDbLoaderDelegate;
+import org.apache.cayenne.dbsync.reverse.DefaultDbLoaderDelegate;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.ObjEntity;
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java
index 18cc197..754071e 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DbImportModule.java
@@ -29,8 +29,8 @@ import org.apache.cayenne.tools.configuration.ToolsModule;
 
 /**
  * A DI module that bootstraps {@link DbImportAction}. Should be used in
- * conjunction with {@link ToolsModule}.
- * 
+ * conjunction with {@link ToolsModule} and {@link org.apache.cayenne.dbsync.CayenneDbSyncModule}.
+ *
  * @since 4.0
  */
 public class DbImportModule implements Module {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
index c51300f..d344946 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/DefaultDbImportAction.java
@@ -18,26 +18,27 @@
  */
 package org.apache.cayenne.tools.dbimport;
 
-import org.apache.cayenne.access.DbLoader;
 import org.apache.cayenne.configuration.ConfigurationTree;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.server.DataSourceFactory;
 import org.apache.cayenne.configuration.server.DbAdapterFactory;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.AbstractToModelToken;
+import org.apache.cayenne.dbsync.merge.AddRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.DbMerger;
+import org.apache.cayenne.dbsync.merge.MergerContext;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.ModelMergeDelegate;
+import org.apache.cayenne.dbsync.merge.ProxyModelMergeDelegate;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.dbsync.reverse.DbLoader;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.MapLoader;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.merge.AbstractToModelToken;
-import org.apache.cayenne.merge.AddRelationshipToDb;
-import org.apache.cayenne.merge.DbMerger;
-import org.apache.cayenne.merge.MergerContext;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.ModelMergeDelegate;
-import org.apache.cayenne.merge.ProxyModelMergeDelegate;
 import org.apache.cayenne.project.Project;
 import org.apache.cayenne.project.ProjectSaver;
 import org.apache.cayenne.resource.URLResource;
@@ -73,17 +74,20 @@ public class DefaultDbImportAction implements DbImportAction {
     private final DataSourceFactory dataSourceFactory;
     private final DbAdapterFactory adapterFactory;
     private final MapLoader mapLoader;
+    private final MergerTokenFactoryProvider mergerTokenFactoryProvider;
 
     public DefaultDbImportAction(@Inject Log logger,
                                  @Inject ProjectSaver projectSaver,
                                  @Inject DataSourceFactory dataSourceFactory,
                                  @Inject DbAdapterFactory adapterFactory,
-                                 @Inject MapLoader mapLoader) {
+                                 @Inject MapLoader mapLoader,
+                                 @Inject MergerTokenFactoryProvider mergerTokenFactoryProvider) {
         this.logger = logger;
         this.projectSaver = projectSaver;
         this.dataSourceFactory = dataSourceFactory;
         this.adapterFactory = adapterFactory;
         this.mapLoader = mapLoader;
+        this.mergerTokenFactoryProvider = mergerTokenFactoryProvider;
     }
 
     protected static List<MergerToken> sort(List<MergerToken> reverse) {
@@ -135,9 +139,9 @@ public class DefaultDbImportAction implements DbImportAction {
 
             saveLoaded(config.initializeDataMap(loadedFomDb));
         } else {
-            MergerFactory mergerFactory = adapter.mergerFactory();
+            MergerTokenFactory mergerTokenFactory = mergerTokenFactoryProvider.get(adapter);
 
-            List<MergerToken> mergeTokens = new DbMerger(mergerFactory)
+            List<MergerToken> mergeTokens = new DbMerger(mergerTokenFactory)
                     .createMergeTokens(existing, loadedFomDb, config.getDbLoaderConfig());
             if (mergeTokens.isEmpty()) {
                 logger.info("");
@@ -157,7 +161,7 @@ public class DefaultDbImportAction implements DbImportAction {
                     super.objEntityAdded(ent);
                 }
 
-            }, existing, log(sort(reverse(mergerFactory, mergeTokens))));
+            }, existing, log(sort(reverse(mergerTokenFactory, mergeTokens))));
 
             DbLoader.flattenManyToManyRelationships(executed, loadedObjEntities, config.getNameGenerator());
             relationshipsSanity(executed);
@@ -207,14 +211,14 @@ public class DefaultDbImportAction implements DbImportAction {
     }
 
     private List<MergerToken> reverse(
-            MergerFactory mergerFactory,
+            MergerTokenFactory mergerTokenFactory,
             Iterable<MergerToken> mergeTokens) throws IOException {
         List<MergerToken> tokens = new LinkedList<>();
         for (MergerToken token : mergeTokens) {
             if (token instanceof AbstractToModelToken) {
                 continue;
             }
-            tokens.add(token.createReverse(mergerFactory));
+            tokens.add(token.createReverse(mergerTokenFactory));
         }
         return tokens;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/config/DefaultTypeMapperBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/config/DefaultTypeMapperBuilder.java b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/config/DefaultTypeMapperBuilder.java
index 52fc3d0..c8cab3a 100644
--- a/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/config/DefaultTypeMapperBuilder.java
+++ b/cayenne-tools/src/main/java/org/apache/cayenne/tools/dbimport/config/DefaultTypeMapperBuilder.java
@@ -18,9 +18,9 @@
  ****************************************************************/
 package org.apache.cayenne.tools.dbimport.config;
 
-import org.apache.cayenne.access.loader.mapper.DbType;
-import org.apache.cayenne.access.loader.mapper.DefaultJdbc2JavaTypeMapper;
-import org.apache.cayenne.access.loader.mapper.Jdbc2JavaTypeMapper;
+import org.apache.cayenne.dbsync.reverse.mapper.DbType;
+import org.apache.cayenne.dbsync.reverse.mapper.DefaultJdbc2JavaTypeMapper;
+import org.apache.cayenne.dbsync.reverse.mapper.Jdbc2JavaTypeMapper;
 import org.apache.commons.lang.ClassUtils;
 import org.apache.commons.logging.Log;
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/test/java/org/apache/cayenne/tools/NamePatternMatcherTest.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/java/org/apache/cayenne/tools/NamePatternMatcherTest.java b/cayenne-tools/src/test/java/org/apache/cayenne/tools/NamePatternMatcherTest.java
index 161f44e..3482843 100644
--- a/cayenne-tools/src/test/java/org/apache/cayenne/tools/NamePatternMatcherTest.java
+++ b/cayenne-tools/src/test/java/org/apache/cayenne/tools/NamePatternMatcherTest.java
@@ -19,12 +19,12 @@
 
 package org.apache.cayenne.tools;
 
-import static org.apache.cayenne.access.loader.NamePatternMatcher.replaceWildcardInStringWithString;
-import static org.junit.Assert.assertEquals;
-
-import org.apache.cayenne.access.loader.NamePatternMatcher;
+import org.apache.cayenne.dbsync.reverse.NamePatternMatcher;
 import org.junit.Test;
 
+import static org.apache.cayenne.dbsync.reverse.NamePatternMatcher.replaceWildcardInStringWithString;
+import static org.junit.Assert.assertEquals;
+
 public class NamePatternMatcherTest {
 
 	/**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportModuleTest.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportModuleTest.java b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportModuleTest.java
index 62b65ea..1e34d4e 100644
--- a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportModuleTest.java
+++ b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DbImportModuleTest.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.tools.dbimport;
 
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
 import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.tools.configuration.ToolsModule;
@@ -33,7 +34,7 @@ public class DbImportModuleTest {
     public void testModuleContents() {
 
         Log log = mock(Log.class);
-        Injector i = DIBootstrap.createInjector(new ToolsModule(log), new DbImportModule());
+        Injector i = DIBootstrap.createInjector(new CayenneDbSyncModule(), new ToolsModule(log), new DbImportModule());
         assertTrue(i.getInstance(DbImportAction.class) instanceof DbImportAction);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DefaultDbImportActionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DefaultDbImportActionTest.java b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DefaultDbImportActionTest.java
index 474b827..5a7c357 100644
--- a/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DefaultDbImportActionTest.java
+++ b/cayenne-tools/src/test/java/org/apache/cayenne/tools/dbimport/DefaultDbImportActionTest.java
@@ -19,26 +19,28 @@
 package org.apache.cayenne.tools.dbimport;
 
 import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.access.DbLoader;
-import org.apache.cayenne.access.DbLoaderDelegate;
-import org.apache.cayenne.access.loader.DbLoaderConfiguration;
 import org.apache.cayenne.configuration.DataNodeDescriptor;
 import org.apache.cayenne.configuration.server.DataSourceFactory;
 import org.apache.cayenne.configuration.server.DbAdapterFactory;
 import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.AddRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.CreateTableToDb;
+import org.apache.cayenne.dbsync.merge.CreateTableToModel;
+import org.apache.cayenne.dbsync.merge.DefaultModelMergeDelegate;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.builders.DataMapBuilder;
+import org.apache.cayenne.dbsync.merge.factory.DefaultMergerTokenFactory;
+import org.apache.cayenne.dbsync.reverse.DbLoader;
+import org.apache.cayenne.dbsync.reverse.DbLoaderConfiguration;
+import org.apache.cayenne.dbsync.reverse.DbLoaderDelegate;
 import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.MapLoader;
-import org.apache.cayenne.merge.AddColumnToDb;
-import org.apache.cayenne.merge.AddRelationshipToDb;
-import org.apache.cayenne.merge.CreateTableToDb;
-import org.apache.cayenne.merge.CreateTableToModel;
-import org.apache.cayenne.merge.DefaultModelMergeDelegate;
-import org.apache.cayenne.merge.MergerFactory;
-import org.apache.cayenne.merge.MergerToken;
-import org.apache.cayenne.merge.builders.DataMapBuilder;
 import org.apache.cayenne.project.FileProjectSaver;
 import org.apache.cayenne.project.Project;
 import org.apache.cayenne.resource.URLResource;
@@ -57,10 +59,10 @@ import java.util.LinkedList;
 import java.util.List;
 
 import static java.util.Arrays.asList;
-import static org.apache.cayenne.merge.builders.ObjectMother.dbAttr;
-import static org.apache.cayenne.merge.builders.ObjectMother.dbEntity;
-import static org.apache.cayenne.merge.builders.ObjectMother.objAttr;
-import static org.apache.cayenne.merge.builders.ObjectMother.objEntity;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbAttr;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbEntity;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.objAttr;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.objEntity;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -287,7 +289,6 @@ public class DefaultDbImportActionTest {
 
     private DefaultDbImportAction buildDbImportAction(Log log, FileProjectSaver projectSaver, MapLoader mapLoader) throws Exception {
         DbAdapter dbAdapter = mock(DbAdapter.class);
-        when(dbAdapter.mergerFactory()).thenReturn(new MergerFactory());
 
 		DbAdapterFactory adapterFactory = mock(DbAdapterFactory.class);
 		when(adapterFactory.createAdapter(any(DataNodeDescriptor.class), any(DataSource.class))).thenReturn(dbAdapter);
@@ -296,13 +297,16 @@ public class DefaultDbImportActionTest {
 		DataSource mock = mock(DataSource.class);
 		when(dataSourceFactory.getDataSource(any(DataNodeDescriptor.class))).thenReturn(mock);
 
-        return new DefaultDbImportAction(log, projectSaver, dataSourceFactory, adapterFactory, mapLoader);
+		MergerTokenFactoryProvider mergerTokenFactoryProvider = mock(MergerTokenFactoryProvider.class);
+		when(mergerTokenFactoryProvider.get(any(DbAdapter.class))).thenReturn(new DefaultMergerTokenFactory());
+
+        return new DefaultDbImportAction(log, projectSaver, dataSourceFactory, adapterFactory, mapLoader, mergerTokenFactoryProvider);
     }
 
 	@Test
 	public void testSaveLoaded() throws Exception {
 		Log log = mock(Log.class);
-		Injector i = DIBootstrap.createInjector(new ToolsModule(log), new DbImportModule());
+		Injector i = DIBootstrap.createInjector(new CayenneDbSyncModule(), new ToolsModule(log), new DbImportModule());
 
         DefaultDbImportAction action = (DefaultDbImportAction) i.getInstance(DbImportAction.class);
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/docs/doc/src/main/resources/RELEASE-NOTES.txt
----------------------------------------------------------------------
diff --git a/docs/doc/src/main/resources/RELEASE-NOTES.txt b/docs/doc/src/main/resources/RELEASE-NOTES.txt
index aa04b5f..fcd7155 100644
--- a/docs/doc/src/main/resources/RELEASE-NOTES.txt
+++ b/docs/doc/src/main/resources/RELEASE-NOTES.txt
@@ -32,6 +32,7 @@ CAY-2107 cayenne-crypto: Lazy initialization of crypto subsystem
 CAY-2111 Unbind transaction object from the current thread for iterated queries
 CAY-2112 Expose callback for "performInTransaction"
 CAY-2113 cdbimport: Reverse-engineering reinstates previously ignored columns
+CAY-2116 Split schema synchronization code in a separate module 
 
 Bug Fixes:
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Main.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Main.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Main.java
index 80f8748..838f736 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Main.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/Main.java
@@ -19,15 +19,8 @@
 
 package org.apache.cayenne.modeler;
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.prefs.Preferences;
-
-import javax.swing.SwingUtilities;
-
 import org.apache.cayenne.configuration.server.ServerModule;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
 import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.di.Module;
@@ -39,6 +32,13 @@ import org.apache.cayenne.project.CayenneProjectModule;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import javax.swing.*;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.prefs.Preferences;
+
 /**
  * Main class responsible for starting CayenneModeler.
  */
@@ -105,6 +105,7 @@ public class Main {
     protected Collection<Module> appendModules(Collection<Module> modules) {
         modules.add(new ServerModule("CayenneModeler"));
         modules.add(new CayenneProjectModule());
+        modules.add(new CayenneDbSyncModule());
         modules.add(new CayenneModelerModule());
 
         return modules;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/CreateObjEntityAction.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/CreateObjEntityAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/CreateObjEntityAction.java
index 7381886..a4f54fc 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/CreateObjEntityAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/CreateObjEntityAction.java
@@ -19,10 +19,9 @@
 
 package org.apache.cayenne.modeler.action;
 
-import java.awt.event.ActionEvent;
-
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.dbsync.merge.EntityMergeSupport;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.ObjEntity;
@@ -37,7 +36,8 @@ import org.apache.cayenne.modeler.event.EntityDisplayEvent;
 import org.apache.cayenne.modeler.undo.CreateObjEntityUndoableEdit;
 import org.apache.cayenne.modeler.util.CayenneAction;
 import org.apache.cayenne.util.DeleteRuleUpdater;
-import org.apache.cayenne.util.EntityMergeSupport;
+
+import java.awt.event.ActionEvent;
 
 public class CreateObjEntityAction extends CayenneAction {
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/DbEntitySyncAction.java
----------------------------------------------------------------------
diff --git a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/DbEntitySyncAction.java b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/DbEntitySyncAction.java
index aec4a2d..65294b5 100644
--- a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/DbEntitySyncAction.java
+++ b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/action/DbEntitySyncAction.java
@@ -19,10 +19,8 @@
 
 package org.apache.cayenne.modeler.action;
 
-import java.awt.event.ActionEvent;
-import java.util.Iterator;
-
 import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.dbsync.merge.EntityMergeSupport;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.event.EntityEvent;
@@ -32,7 +30,9 @@ import org.apache.cayenne.modeler.ProjectController;
 import org.apache.cayenne.modeler.dialog.objentity.EntitySyncController;
 import org.apache.cayenne.modeler.undo.DbEntitySyncUndoableEdit;
 import org.apache.cayenne.modeler.util.CayenneAction;
-import org.apache.cayenne.util.EntityMergeSupport;
+
+import java.awt.event.ActionEvent;
+import java.util.Iterator;
 
 /**
  * Action that synchronizes all ObjEntities with the current state of the


[05/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index 3070cf1..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMerger.java
+++ /dev/null
@@ -1,405 +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.merge;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.Types;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-
-import javax.sql.DataSource;
-
-import org.apache.cayenne.CayenneRuntimeException;
-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.LoggingDbLoaderDelegate;
-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;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.DetectedDbEntity;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-/**
- * Traverse a {@link DataNode} and a {@link DataMap} and create a group of
- * {@link MergerToken}s to alter the {@link DataNode} data store to match the
- * {@link DataMap}.
- * 
- */
-public class DbMerger {
-
-	private static final Log LOGGER = LogFactory.getLog(DbMerger.class);
-
-	private final MergerFactory factory;
-
-	private final ValueForNullProvider valueForNull;
-
-	public DbMerger(MergerFactory factory) {
-		this(factory, null);
-	}
-
-	public DbMerger(MergerFactory factory, ValueForNullProvider valueForNull) {
-		this.factory = factory;
-		this.valueForNull = valueForNull == null ? new EmptyValueForNullProvider() : valueForNull;
-	}
-
-	/**
-	 * 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,
-			DbLoaderConfiguration config) {
-		return createMergeTokens(existingDataMap, loadDataMapFromDb(dataSource, adapter, config), 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(DataMap existing, DataMap loadedFomDb, DbLoaderConfiguration config) {
-
-		loadedFomDb.setQuotingSQLIdentifiers(existing.isQuotingSQLIdentifiers());
-
-		List<MergerToken> tokens = createMergeTokens(filter(existing, config.getFiltersConfig()),
-				loadedFomDb.getDbEntities(), config);
-
-		// sort. use a custom Comparator since only toDb tokens are comparable
-		// by now
-		Collections.sort(tokens, new Comparator<MergerToken>() {
-
-			public int compare(MergerToken o1, MergerToken o2) {
-				if (o1 instanceof AbstractToDbToken && o2 instanceof AbstractToDbToken) {
-
-					return ((AbstractToDbToken) o1).compareTo(o2);
-				}
-				return 0;
-			}
-		});
-
-		return tokens;
-	}
-
-	private Collection<DbEntity> filter(DataMap existing, FiltersConfig filtersConfig) {
-		Collection<DbEntity> existingFiltered = new LinkedList<DbEntity>();
-		for (DbEntity entity : existing.getDbEntities()) {
-			if (filtersConfig.tableFilter(entity.getCatalog(), entity.getSchema()).isIncludeTable(entity.getName()) != null) {
-				existingFiltered.add(entity);
-			}
-		}
-		return existingFiltered;
-	}
-
-	private DataMap loadDataMapFromDb(DataSource dataSource, DbAdapter adapter, DbLoaderConfiguration config) {
-		try (Connection conn = dataSource.getConnection();) {
-
-			return new DbLoader(conn, adapter, new LoggingDbLoaderDelegate(LOGGER)).load(config);
-		} catch (SQLException e) {
-			throw new CayenneRuntimeException("Can't doLoad dataMap from db.", e);
-		}
-	}
-
-	public List<MergerToken> createMergeTokens(Collection<DbEntity> existing, Collection<DbEntity> loadedFromDb,
-			DbLoaderConfiguration config) {
-		Collection<DbEntity> dbEntitiesToDrop = new LinkedList<DbEntity>(loadedFromDb);
-
-		List<MergerToken> tokens = new LinkedList<MergerToken>();
-		for (DbEntity dbEntity : existing) {
-			String tableName = dbEntity.getName();
-
-			// look for table
-			DbEntity detectedEntity = findDbEntity(loadedFromDb, tableName);
-			if (detectedEntity == null) {
-				tokens.add(factory.createCreateTableToDb(dbEntity));
-				// TODO: does this work properly with createReverse?
-				for (DbRelationship rel : dbEntity.getRelationships()) {
-					tokens.add(factory.createAddRelationshipToDb(dbEntity, rel));
-				}
-				continue;
-			}
-
-			dbEntitiesToDrop.remove(detectedEntity);
-
-			tokens.addAll(checkRelationshipsToDrop(dbEntity, detectedEntity));
-			if (!config.isSkipRelationshipsLoading()) {
-				tokens.addAll(checkRelationshipsToAdd(dbEntity, detectedEntity));
-			}
-			tokens.addAll(checkRows(dbEntity, detectedEntity));
-
-			if (!config.isSkipPrimaryKeyLoading()) {
-				MergerToken token = checkPrimaryKeyChange(dbEntity, detectedEntity);
-				if (token != null) {
-					tokens.add(token);
-				}
-			}
-		}
-
-		// drop table
-		// TODO: support drop table. currently, too many tables are marked for
-		// drop
-		for (DbEntity e : dbEntitiesToDrop) {
-			tokens.add(factory.createDropTableToDb(e));
-			for (DbRelationship relationship : e.getRelationships()) {
-				DbEntity detectedEntity = findDbEntity(existing, relationship.getTargetEntityName());
-				if (detectedEntity != null) {
-					tokens.add(factory.createDropRelationshipToDb(detectedEntity, relationship.getReverseRelationship()));
-				}
-			}
-		}
-
-		return tokens;
-	}
-
-	private List<MergerToken> checkRows(DbEntity existing, DbEntity loadedFromDb) {
-		List<MergerToken> tokens = new LinkedList<MergerToken>();
-
-		// columns to drop
-		for (DbAttribute detected : loadedFromDb.getAttributes()) {
-			if (findDbAttribute(existing, detected.getName()) == null) {
-				tokens.add(factory.createDropColumnToDb(existing, detected));
-			}
-		}
-
-		// columns to add or modify
-		for (DbAttribute attr : existing.getAttributes()) {
-			String columnName = attr.getName().toUpperCase();
-
-			DbAttribute detected = findDbAttribute(loadedFromDb, columnName);
-
-			if (detected == null) {
-				tokens.add(factory.createAddColumnToDb(existing, attr));
-				if (attr.isMandatory()) {
-					if (valueForNull.hasValueFor(existing, attr)) {
-						tokens.add(factory.createSetValueForNullToDb(existing, attr, valueForNull));
-					}
-					tokens.add(factory.createSetNotNullToDb(existing, attr));
-				}
-				continue;
-			}
-
-			// check for not null
-			if (attr.isMandatory() != detected.isMandatory()) {
-				if (attr.isMandatory()) {
-					if (valueForNull.hasValueFor(existing, attr)) {
-						tokens.add(factory.createSetValueForNullToDb(existing, attr, valueForNull));
-					}
-					tokens.add(factory.createSetNotNullToDb(existing, attr));
-				} else {
-					tokens.add(factory.createSetAllowNullToDb(existing, attr));
-				}
-			}
-
-			// TODO: check more types than char/varchar
-			// TODO: psql report VARCHAR for text column, not clob
-			switch (detected.getType()) {
-			case Types.VARCHAR:
-			case Types.CHAR:
-				if (attr.getMaxLength() != detected.getMaxLength()) {
-					tokens.add(factory.createSetColumnTypeToDb(existing, detected, attr));
-				}
-				break;
-			}
-		}
-
-		return tokens;
-	}
-
-	private List<MergerToken> checkRelationshipsToDrop(DbEntity dbEntity, DbEntity detectedEntity) {
-		List<MergerToken> tokens = new LinkedList<MergerToken>();
-
-		// relationships to drop
-		for (DbRelationship detected : detectedEntity.getRelationships()) {
-			if (findDbRelationship(dbEntity, detected) == null) {
-
-				// alter detected relationship to match entity and attribute
-				// names.
-				// (case sensitively)
-
-				DbEntity targetEntity = findDbEntity(dbEntity.getDataMap().getDbEntities(),
-						detected.getTargetEntityName());
-				if (targetEntity == null) {
-					continue;
-				}
-
-				detected.setSourceEntity(dbEntity);
-				detected.setTargetEntityName(targetEntity);
-
-				// manipulate the joins to match the DbAttributes in the model
-				for (DbJoin join : detected.getJoins()) {
-					DbAttribute sattr = findDbAttribute(dbEntity, join.getSourceName());
-					if (sattr != null) {
-						join.setSourceName(sattr.getName());
-					}
-					DbAttribute tattr = findDbAttribute(targetEntity, join.getTargetName());
-					if (tattr != null) {
-						join.setTargetName(tattr.getName());
-					}
-				}
-
-				MergerToken token = factory.createDropRelationshipToDb(dbEntity, detected);
-				if (detected.isToMany()) {
-					// default toModel as we can not do drop a toMany in the db.
-					// only
-					// toOne are represented using foreign key
-					token = token.createReverse(factory);
-				}
-				tokens.add(token);
-			}
-		}
-
-		return tokens;
-	}
-
-	private List<MergerToken> checkRelationshipsToAdd(DbEntity dbEntity, DbEntity detectedEntity) {
-
-		List<MergerToken> tokens = new LinkedList<MergerToken>();
-
-		for (DbRelationship rel : dbEntity.getRelationships()) {
-			if (findDbRelationship(detectedEntity, rel) == null) {
-				AddRelationshipToDb token = (AddRelationshipToDb) factory.createAddRelationshipToDb(dbEntity, rel);
-
-				if (token.shouldGenerateFkConstraint()) {
-					// TODO I guess we should add relationship always; in order
-					// to have ability
-					// TODO generate reverse relationship. If it doesn't have
-					// anything to execute it will be passed
-					// TODO through execution without any affect on db
-					tokens.add(token);
-				}
-			}
-		}
-
-		return tokens;
-	}
-
-	private MergerToken checkPrimaryKeyChange(DbEntity dbEntity, DbEntity detectedEntity) {
-		Collection<DbAttribute> primaryKeyOriginal = detectedEntity.getPrimaryKeys();
-		Collection<DbAttribute> primaryKeyNew = dbEntity.getPrimaryKeys();
-
-		String primaryKeyName = null;
-		if (detectedEntity instanceof DetectedDbEntity) {
-			primaryKeyName = ((DetectedDbEntity) detectedEntity).getPrimaryKeyName();
-		}
-
-		if (upperCaseEntityNames(primaryKeyOriginal).equals(upperCaseEntityNames(primaryKeyNew))) {
-			return null;
-		}
-
-		return factory.createSetPrimaryKeyToDb(dbEntity, primaryKeyOriginal, primaryKeyNew, primaryKeyName);
-	}
-
-	private Set<String> upperCaseEntityNames(Collection<? extends Attribute> attrs) {
-		Set<String> names = new HashSet<String>();
-		for (Attribute attr : attrs) {
-			names.add(attr.getName().toUpperCase());
-		}
-		return names;
-	}
-
-	/**
-	 * case insensitive search for a {@link DbEntity} in a {@link DataMap} by
-	 * name
-	 */
-	private DbEntity findDbEntity(Collection<DbEntity> dbEntities, String caseInsensitiveName) {
-		// TODO: create a Map with upper case keys?
-		for (DbEntity e : dbEntities) {
-			if (e.getName().equalsIgnoreCase(caseInsensitiveName)) {
-				return e;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * case insensitive search for a {@link DbAttribute} in a {@link DbEntity}
-	 * by name
-	 */
-	private DbAttribute findDbAttribute(DbEntity entity, String caseInsensitiveName) {
-		for (DbAttribute a : entity.getAttributes()) {
-			if (a.getName().equalsIgnoreCase(caseInsensitiveName)) {
-				return a;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * search for a {@link DbRelationship} like rel in the given
-	 * {@link DbEntity}
-	 */
-	private DbRelationship findDbRelationship(DbEntity entity, DbRelationship rel) {
-		for (DbRelationship candidate : entity.getRelationships()) {
-			if (equalDbJoinCollections(candidate.getJoins(), rel.getJoins())) {
-				return candidate;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Return true if the two unordered {@link Collection}s of {@link DbJoin}s
-	 * are equal. Entity and Attribute names are compared case insensitively.
-	 *
-	 * TODO complexity n^2; sort both collection and go through them to compare
-	 * = 2*n*log(n) + n
-	 */
-	private static boolean equalDbJoinCollections(Collection<DbJoin> j1s, Collection<DbJoin> j2s) {
-		if (j1s.size() != j2s.size()) {
-			return false;
-		}
-
-		for (DbJoin j1 : j1s) {
-			if (!havePair(j2s, j1)) {
-				return false;
-			}
-		}
-
-		return true;
-	}
-
-	private static boolean havePair(Collection<DbJoin> j2s, DbJoin j1) {
-		for (DbJoin j2 : j2s) {
-			if (!isNull(j1.getSource()) && !isNull(j1.getTarget()) && !isNull(j2.getSource())
-					&& !isNull(j2.getTarget())
-					&& j1.getSource().getEntity().getName().equalsIgnoreCase(j2.getSource().getEntity().getName())
-					&& j1.getTarget().getEntity().getName().equalsIgnoreCase(j2.getTarget().getEntity().getName())
-					&& j1.getSourceName().equalsIgnoreCase(j2.getSourceName())
-					&& j1.getTargetName().equalsIgnoreCase(j2.getTargetName())) {
-
-				return true;
-			}
-		}
-		return false;
-	}
-
-	private static boolean isNull(DbAttribute attribute) {
-		return attribute == null || attribute.getEntity() == null;
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMergerConfig.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMergerConfig.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMergerConfig.java
deleted file mode 100644
index e8df4b8..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DbMergerConfig.java
+++ /dev/null
@@ -1,63 +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.merge;
-
-import org.apache.cayenne.access.loader.filters.FiltersConfig;
-
-/**
- * @since 4.0
- */
-public class DbMergerConfig {
-
-    private FiltersConfig filtersConfig;
-
-    private boolean skipRelationships;
-
-    private boolean skipPrimaryKey;
-
-    public DbMergerConfig(FiltersConfig filtersConfig, boolean skipRelationships, boolean skipPrimaryKey) {
-        this.filtersConfig = filtersConfig;
-        this.skipRelationships = skipRelationships;
-        this.skipPrimaryKey = skipPrimaryKey;
-    }
-
-    public void setSkipRelationships(boolean skipRelationships) {
-        this.skipRelationships = skipRelationships;
-    }
-
-    public boolean isSkipRelationships() {
-        return skipRelationships;
-    }
-
-    public void setSkipPrimaryKey(boolean skipPrimaryKey) {
-        this.skipPrimaryKey = skipPrimaryKey;
-    }
-
-    public boolean isSkipPrimaryKey() {
-        return skipPrimaryKey;
-    }
-
-    public FiltersConfig getFiltersConfig() {
-        return filtersConfig;
-    }
-
-    public void setFiltersConfig(FiltersConfig filtersConfig) {
-        this.filtersConfig = filtersConfig;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DefaultModelMergeDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DefaultModelMergeDelegate.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DefaultModelMergeDelegate.java
deleted file mode 100644
index e457f31..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DefaultModelMergeDelegate.java
+++ /dev/null
@@ -1,89 +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.merge;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-
-/**
- * A default noop implementation of {@link ModelMergeDelegate}.
- */
-public class DefaultModelMergeDelegate implements ModelMergeDelegate {
-
-    @Override
-    public void dbAttributeAdded(DbAttribute att) {
-    }
-
-    @Override
-    public void dbAttributeModified(DbAttribute att) {
-    }
-
-    @Override
-    public void dbAttributeRemoved(DbAttribute att) {
-    }
-
-    @Override
-    public void dbEntityAdded(DbEntity ent) {
-    }
-
-    @Override
-    public void dbEntityRemoved(DbEntity ent) {
-    }
-
-    @Override
-    public void dbRelationshipAdded(DbRelationship rel) {
-    }
-
-    @Override
-    public void dbRelationshipRemoved(DbRelationship rel) {
-    }
-
-    @Override
-    public void objAttributeAdded(ObjAttribute att) {
-    }
-
-    @Override
-    public void objAttributeModified(ObjAttribute att) {
-    }
-
-    @Override
-    public void objAttributeRemoved(ObjAttribute att) {
-    }
-
-    @Override
-    public void objEntityAdded(ObjEntity ent) {
-    }
-
-    @Override
-    public void objEntityRemoved(ObjEntity ent) {
-    }
-
-    @Override
-    public void objRelationshipAdded(ObjRelationship rel) {
-    }
-
-    @Override
-    public void objRelationshipRemoved(ObjRelationship rel) {
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DefaultValueForNullProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DefaultValueForNullProvider.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DefaultValueForNullProvider.java
deleted file mode 100644
index f17f0fc..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DefaultValueForNullProvider.java
+++ /dev/null
@@ -1,62 +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.merge;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.cayenne.access.jdbc.SQLParameterBinding;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-public class DefaultValueForNullProvider implements ValueForNullProvider {
-
-    private Map<String, SQLParameterBinding> values = new HashMap<>();
-
-    public void set(DbEntity entity, DbAttribute column, Object value, int type) {
-        values.put(createKey(entity, column), new SQLParameterBinding(value, type, column
-                .getAttributePrecision()));
-    }
-
-    protected SQLParameterBinding get(DbEntity entity, DbAttribute column) {
-        return values.get(createKey(entity, column));
-    }
-
-    public List<String> createSql(DbEntity entity, DbAttribute column) {
-        SQLParameterBinding value = get(entity, column);
-        if (value == null) {
-            return Collections.emptyList();
-        }
-
-        // TODO: change things so it is possible to use prepared statements here
-        return Collections.singletonList("UPDATE " + entity.getFullyQualifiedName()
-                + " SET " + column.getName() + "='" + value.getValue() + "' WHERE " + column.getName() + " IS NULL");
-    }
-
-    public boolean hasValueFor(DbEntity entity, DbAttribute column) {
-        return values.containsKey(createKey(entity, column));
-    }
-
-    private String createKey(DbEntity entity, DbAttribute attribute) {
-        return (entity.getFullyQualifiedName() + "." + attribute.getName()).toUpperCase();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DropColumnToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropColumnToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DropColumnToDb.java
deleted file mode 100644
index 5cb0a05..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropColumnToDb.java
+++ /dev/null
@@ -1,51 +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.merge;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-public class DropColumnToDb extends AbstractToDbToken.EntityAndColumn {
-
-    public DropColumnToDb(DbEntity entity, DbAttribute column) {
-        super("Drop Column", entity, column);
-    }
-
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        StringBuilder sqlBuffer = new StringBuilder();
-        QuotingStrategy context = adapter.getQuotingStrategy();
-        sqlBuffer.append("ALTER TABLE ");
-        sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-        sqlBuffer.append(" DROP COLUMN ");
-        sqlBuffer.append(context.quotedName(getColumn()));
-
-        return Collections.singletonList(sqlBuffer.toString());
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createAddColumnToModel(getEntity(), getColumn());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DropColumnToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropColumnToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DropColumnToModel.java
deleted file mode 100644
index 37fb728..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropColumnToModel.java
+++ /dev/null
@@ -1,74 +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.merge;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-
-/**
- * A {@link MergerToken} to remove a {@link DbAttribute} from a {@link DbEntity}.
- * 
- */
-public class DropColumnToModel extends AbstractToModelToken.EntityAndColumn {
-
-    public DropColumnToModel(DbEntity entity, DbAttribute column) {
-        super("Drop Column", entity, column);
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createAddColumnToDb(getEntity(), getColumn());
-    }
-
-    public void execute(MergerContext mergerContext) {
-
-        // remove relationships mapped to column. duplicate List to prevent
-        // ConcurrentModificationException
-        List<DbRelationship> dbRelationships = new ArrayList<DbRelationship>(getEntity()
-                .getRelationships());
-        for (DbRelationship dbRelationship : dbRelationships) {
-            for (DbJoin join : dbRelationship.getJoins()) {
-                if (join.getSource() == getColumn() || join.getTarget() == getColumn()) {
-                    remove(mergerContext.getModelMergeDelegate(), dbRelationship, true);
-                }
-            }
-        }
-
-        // remove ObjAttribute mapped to same column
-        for (ObjEntity objEntity : getEntity().mappedObjEntities()) {
-            ObjAttribute objAttribute = objEntity.getAttributeForDbAttribute(getColumn());
-            if (objAttribute != null) {
-                objEntity.removeAttribute(objAttribute.getName());
-                mergerContext.getModelMergeDelegate().objAttributeRemoved(objAttribute);
-            }
-
-        }
-
-        // remove DbAttribute
-        getEntity().removeAttribute(getColumn().getName());
-
-        mergerContext.getModelMergeDelegate().dbAttributeRemoved(getColumn());
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DropRelationshipToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropRelationshipToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DropRelationshipToDb.java
deleted file mode 100644
index 618aab3..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropRelationshipToDb.java
+++ /dev/null
@@ -1,71 +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.merge;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.DbRelationshipDetected;
-
-public class DropRelationshipToDb extends AbstractToDbToken.Entity {
-
-    private DbRelationship rel;
-
-    public DropRelationshipToDb(DbEntity entity, DbRelationship rel) {
-        super("Drop foreign key", entity);
-        this.rel = rel;
-    }
-    
-    public String getFkName() {
-        if (rel instanceof DbRelationshipDetected) {
-            return ((DbRelationshipDetected) rel).getFkName();
-        }
-        return null;
-    }
-
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        String fkName = getFkName();
-        if (fkName == null) {
-            return Collections.emptyList();
-        }
-
-        QuotingStrategy context = adapter.getQuotingStrategy();
-        return Collections.singletonList(
-                "ALTER TABLE " + context.quotedFullyQualifiedName(getEntity()) + " DROP CONSTRAINT " + fkName);
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createAddRelationshipToModel(getEntity(), rel);
-    }
-
-    @Override
-    public String getTokenValue() {
-        return rel.getSourceEntity().getName() + "->" + rel.getTargetEntityName();
-    }
-    
-    public DbRelationship getRelationship() {
-        return rel;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DropRelationshipToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropRelationshipToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DropRelationshipToModel.java
deleted file mode 100644
index ee9359f..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropRelationshipToModel.java
+++ /dev/null
@@ -1,50 +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.merge;
-
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-
-public class DropRelationshipToModel extends AbstractToModelToken.Entity {
-
-    private final DbRelationship rel;
-
-    public DropRelationshipToModel(DbEntity entity, DbRelationship rel) {
-        super("Drop db-relationship ", entity);
-        this.rel = rel;
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createAddRelationshipToDb(getEntity(), rel);
-    }
-
-    public void execute(MergerContext mergerContext) {
-        remove(mergerContext.getModelMergeDelegate(), rel, true);
-    }
-
-    @Override
-    public String getTokenValue() {
-        return AddRelationshipToModel.getTokenValue(rel);
-    }
-    
-    public DbRelationship getRelationship() {
-        return rel;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DropTableToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropTableToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DropTableToDb.java
deleted file mode 100644
index d0904e6..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropTableToDb.java
+++ /dev/null
@@ -1,49 +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.merge;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbEntity;
-
-public class DropTableToDb extends AbstractToDbToken.Entity {
-
-    public DropTableToDb(DbEntity entity) {
-        super("Drop Table", entity);
-    }
-
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        List<String> sqls = new ArrayList<String>();
-        // TODO: fix. some adapters drop the complete AUTO_PK_SUPPORT here
-        /*
-        sqls.addAll(adapter.getPkGenerator().dropAutoPkStatements(
-                Collections.singletonList(entity)));
-         */
-        sqls.addAll(adapter.dropTableStatements(getEntity()));
-        return sqls;
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createCreateTableToModel(getEntity());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DropTableToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropTableToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DropTableToModel.java
deleted file mode 100644
index 55fde14..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DropTableToModel.java
+++ /dev/null
@@ -1,48 +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.merge;
-
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.ObjEntity;
-
-/**
- * A {@link MergerToken} to remove a {@link DbEntity} from a {@link DataMap}. Any
- * {@link ObjEntity} mapped to the {@link DbEntity} will also be removed.
- * 
- */
-public class DropTableToModel extends AbstractToModelToken.Entity {
-
-    public DropTableToModel(DbEntity entity) {
-        super("Drop Table", entity);
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createCreateTableToDb(getEntity());
-    }
-
-    public void execute(MergerContext mergerContext) {
-        for (ObjEntity objEntity : getEntity().mappedObjEntities()) {
-            objEntity.getDataMap().removeObjEntity(objEntity.getName(), true);
-            mergerContext.getModelMergeDelegate().objEntityRemoved(objEntity);
-        }
-        getEntity().getDataMap().removeDbEntity(getEntity().getName(), true);
-        mergerContext.getModelMergeDelegate().dbEntityRemoved(getEntity());
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/DummyReverseToken.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/DummyReverseToken.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/DummyReverseToken.java
deleted file mode 100644
index b99cc97..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/DummyReverseToken.java
+++ /dev/null
@@ -1,58 +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.merge;
-
-/**
- * The reverse of a {@link MergerToken} that can not be reversed.. This will not execute
- * any thing, but {@link #createReverse(MergerFactory)} will get back the reverse that
- * this was made from.
- */
-class DummyReverseToken implements MergerToken {
-
-    private MergerToken reverse;
-
-    public DummyReverseToken(MergerToken reverse) {
-        this.reverse = reverse;
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return reverse;
-    }
-
-    public void execute(MergerContext mergerContext) {
-        // can not execute
-    }
-
-    public MergeDirection getDirection() {
-        return reverse.getDirection().reverseDirection();
-    }
-
-    public String getTokenName() {
-        return "Can not execute the reverse of " + reverse.getTokenName();
-    }
-
-    public String getTokenValue() {
-        return reverse.getTokenValue();
-    }
-
-    public boolean isReversible() {
-        return true;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/EmptyValueForNullProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/EmptyValueForNullProvider.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/EmptyValueForNullProvider.java
deleted file mode 100644
index 88fb9f4..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/EmptyValueForNullProvider.java
+++ /dev/null
@@ -1,40 +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.merge;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-/**
- * A dummy {@link ValueForNullProvider} that are not able to provide any values
- */
-class EmptyValueForNullProvider implements ValueForNullProvider {
-
-    public List<String> createSql(DbEntity entity, DbAttribute column) {
-        return Collections.emptyList();
-    }
-
-    public boolean hasValueFor(DbEntity entity, DbAttribute column) {
-        return false;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/MergeDirection.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/MergeDirection.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/MergeDirection.java
deleted file mode 100644
index d0a95c5..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/MergeDirection.java
+++ /dev/null
@@ -1,70 +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.merge;
-
-/**
- * Represent a merge direction that can be either from the model to the db or from the db to the model.
- */
-public enum MergeDirection {
-
-    /**
-     * TO_DB Token means that changes was made in object model and should be reflected at DB
-     */
-    TO_DB("To DB"),
-
-    /**
-     * TO_MODEL Token represent database changes that should be allayed to object model
-     */
-    TO_MODEL("To Model");
-
-    private String name;
-
-    MergeDirection(String name) {
-        this.name = name;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public boolean isToDb() {
-        return (this == TO_DB);
-    }
-
-    public boolean isToModel() {
-        return (this == TO_MODEL);
-    }
-
-    @Override
-    public String toString() {
-        return getName();
-    }
-
-    public MergeDirection reverseDirection() {
-        switch (this) {
-            case TO_DB:
-                return TO_MODEL;
-            case TO_MODEL:
-                return TO_DB;
-            default:
-                throw new IllegalStateException("Invalid direction: " + this);
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerContext.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerContext.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerContext.java
deleted file mode 100644
index f592ebe..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerContext.java
+++ /dev/null
@@ -1,118 +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.merge;
-
-import org.apache.cayenne.access.DataNode;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.validation.ValidationResult;
-
-import javax.sql.DataSource;
-import java.util.Objects;
-
-/**
- * An object passed as an argument to {@link MergerToken#execute(MergerContext)}s that a
- * {@link MergerToken} can do its work.
- */
-public class MergerContext {
-
-    private DataMap dataMap;
-    private DataNode dataNode;
-    private ValidationResult validationResult;
-    private ModelMergeDelegate delegate;
-
-    protected MergerContext() {
-    }
-
-    public static Builder builder(DataMap dataMap) {
-        return new Builder().dataMap(dataMap);
-    }
-
-    /**
-     * @deprecated since 4.0 use {@link #getDataNode()} and its {@link DataNode#getAdapter()} method.
-     */
-    @Deprecated
-    public DbAdapter getAdapter() {
-        return getDataNode().getAdapter();
-    }
-
-    /**
-     * Returns the DataMap that is the target of a the merge operation.
-     *
-     * @return the DataMap that is the target of a the merge operation.
-     */
-    public DataMap getDataMap() {
-        return dataMap;
-    }
-
-    public DataNode getDataNode() {
-        return dataNode;
-    }
-
-    public ValidationResult getValidationResult() {
-        return validationResult;
-    }
-
-    /**
-     * Returns a callback object that is invoked as the merge proceeds through tokens, modifying the DataMap.
-     *
-     * @return a callback object that is invoked as the merge proceeds through tokens, modifying the DataMap.
-     */
-    public ModelMergeDelegate getModelMergeDelegate() {
-        return delegate;
-    }
-
-    public static class Builder {
-
-        private MergerContext context;
-
-        private Builder() {
-            this.context = new MergerContext();
-            this.context.validationResult = new ValidationResult();
-            this.context.delegate = new DefaultModelMergeDelegate();
-            this.context.dataNode = new DataNode();
-        }
-
-        public MergerContext build() {
-            return context;
-        }
-
-        public Builder delegate(ModelMergeDelegate delegate) {
-            context.delegate = Objects.requireNonNull(delegate);
-            return this;
-        }
-
-        public Builder dataNode(DataNode dataNode) {
-            this.context.dataNode = Objects.requireNonNull(dataNode);
-            return this;
-        }
-
-        public Builder syntheticDataNode(DataSource dataSource, DbAdapter adapter) {
-            DataNode dataNode = new DataNode();
-            dataNode.setDataSource(dataSource);
-            dataNode.setAdapter(adapter);
-            return dataNode(dataNode);
-        }
-
-        public Builder dataMap(DataMap dataMap) {
-            context.dataMap = Objects.requireNonNull(dataMap);
-            return this;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerFactory.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerFactory.java
deleted file mode 100644
index 92e064b..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerFactory.java
+++ /dev/null
@@ -1,142 +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.merge;
-
-import java.util.Collection;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-
-/**
- * All {@link MergerToken}s should be created from a {@link MergerFactory} obtained from
- * {@link DbAdapter#mergerFactory()} so that the {@link DbAdapter} are able to provide
- * {@link MergerToken} subclasses.
- * 
- * @see DbAdapter#mergerFactory()
- */
-public class MergerFactory {
-
-    public MergerToken createCreateTableToModel(DbEntity entity) {
-        return new CreateTableToModel(entity);
-    }
-
-    public MergerToken createCreateTableToDb(DbEntity entity) {
-        return new CreateTableToDb(entity);
-    }
-
-    public MergerToken createDropTableToModel(DbEntity entity) {
-        return new DropTableToModel(entity);
-    }
-
-    public MergerToken createDropTableToDb(DbEntity entity) {
-        return new DropTableToDb(entity);
-    }
-
-    public MergerToken createAddColumnToModel(DbEntity entity, DbAttribute column) {
-        return new AddColumnToModel(entity, column);
-    }
-
-    public MergerToken createAddColumnToDb(DbEntity entity, DbAttribute column) {
-        return new AddColumnToDb(entity, column);
-    }
-
-    public MergerToken createDropColumnToModel(DbEntity entity, DbAttribute column) {
-        return new DropColumnToModel(entity, column);
-    }
-
-    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
-        return new DropColumnToDb(entity, column);
-    }
-
-    public MergerToken createSetNotNullToModel(DbEntity entity, DbAttribute column) {
-        return new SetNotNullToModel(entity, column);
-    }
-
-    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetNotNullToDb(entity, column);
-    }
-
-    public MergerToken createSetAllowNullToModel(DbEntity entity, DbAttribute column) {
-        return new SetAllowNullToModel(entity, column);
-    }
-
-    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
-        return new SetAllowNullToDb(entity, column);
-    }
-    
-    public MergerToken createSetValueForNullToDb(DbEntity entity, DbAttribute column, ValueForNullProvider valueForNullProvider){
-        return new SetValueForNullToDb(entity, column, valueForNullProvider);
-    }
-
-    public MergerToken createSetColumnTypeToModel(
-            DbEntity entity,
-            DbAttribute columnOriginal,
-            DbAttribute columnNew) {
-        return new SetColumnTypeToModel(entity, columnOriginal, columnNew);
-    }
-
-    public MergerToken createSetColumnTypeToDb(
-            DbEntity entity,
-            DbAttribute columnOriginal,
-            DbAttribute columnNew) {
-        return new SetColumnTypeToDb(entity, columnOriginal, columnNew);
-    }
-    
-    public MergerToken createAddRelationshipToDb(DbEntity entity, DbRelationship rel) {
-        return new AddRelationshipToDb(entity, rel);
-    }
-
-    public MergerToken createAddRelationshipToModel(DbEntity entity, DbRelationship rel) {
-        return new AddRelationshipToModel(entity, rel);
-    }
-
-    public MergerToken createDropRelationshipToDb(DbEntity entity, DbRelationship rel) {
-        return new DropRelationshipToDb(entity, rel);
-    }
-
-    public MergerToken createDropRelationshipToModel(DbEntity entity, DbRelationship rel) {
-        return new DropRelationshipToModel(entity, rel);
-    }
-    
-    public MergerToken createSetPrimaryKeyToDb(
-            DbEntity entity,
-            Collection<DbAttribute> primaryKeyOriginal,
-            Collection<DbAttribute> primaryKeyNew,
-            String detectedPrimaryKeyName) {
-        return new SetPrimaryKeyToDb(
-                entity,
-                primaryKeyOriginal,
-                primaryKeyNew,
-                detectedPrimaryKeyName);
-    }
-
-    public MergerToken createSetPrimaryKeyToModel(
-            DbEntity entity,
-            Collection<DbAttribute> primaryKeyOriginal,
-            Collection<DbAttribute> primaryKeyNew,
-            String detectedPrimaryKeyName) {
-        return new SetPrimaryKeyToModel(
-                entity,
-                primaryKeyOriginal,
-                primaryKeyNew,
-                detectedPrimaryKeyName);
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerToken.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerToken.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerToken.java
deleted file mode 100644
index 99af419..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/MergerToken.java
+++ /dev/null
@@ -1,51 +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.merge;
-
-/**
- * Represents a minimal atomic synchronization operation between database and Cayenne model.
- */
-public interface MergerToken {
-
-    String getTokenName();
-
-    String getTokenValue();
-
-    /**
-     * The direction of this token. One of {@link MergeDirection#TO_DB} or
-     * {@link MergeDirection#TO_MODEL}
-     */
-    MergeDirection getDirection();
-
-    /**
-     * Create a complimentary token with the reverse direction. AddColumn in one direction becomes
-     * DropColumn in the other direction.
-     * <p>
-     * Not all tokens are reversible.
-     */
-    MergerToken createReverse(MergerFactory factory);
-
-    /**
-     * Executes synchronization operation.
-     *
-     * @param mergerContext operation context.
-     */
-    void execute(MergerContext mergerContext);
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/ModelMergeDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/ModelMergeDelegate.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/ModelMergeDelegate.java
deleted file mode 100644
index f003752..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/ModelMergeDelegate.java
+++ /dev/null
@@ -1,65 +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.merge;
-
-import org.apache.cayenne.access.DbLoaderDelegate;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-
-/**
- * A interface used to tell about modifications performed on the model by
- * {@link MergerToken} with {@link MergeDirection#TO_MODEL}
- * 
- * @see DbLoaderDelegate
- */
-public interface ModelMergeDelegate {
-
-    public void dbEntityAdded(DbEntity ent);
-
-    public void dbEntityRemoved(DbEntity ent);
-
-    public void objEntityAdded(ObjEntity ent);
-
-    public void objEntityRemoved(ObjEntity ent);
-
-    public void dbAttributeAdded(DbAttribute att);
-
-    public void dbAttributeRemoved(DbAttribute att);
-
-    public void dbAttributeModified(DbAttribute att);
-
-    public void objAttributeAdded(ObjAttribute att);
-
-    public void objAttributeRemoved(ObjAttribute att);
-
-    public void objAttributeModified(ObjAttribute att);
-
-    public void dbRelationshipAdded(DbRelationship rel);
-
-    public void dbRelationshipRemoved(DbRelationship rel);
-
-    public void objRelationshipAdded(ObjRelationship rel);
-
-    public void objRelationshipRemoved(ObjRelationship rel);
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/ProxyModelMergeDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/ProxyModelMergeDelegate.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/ProxyModelMergeDelegate.java
deleted file mode 100644
index ad6a4a9..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/ProxyModelMergeDelegate.java
+++ /dev/null
@@ -1,108 +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.merge;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-
-/**
- * @since 4.0
- */
-public class ProxyModelMergeDelegate implements ModelMergeDelegate {
-
-    private final ModelMergeDelegate delegate;
-
-    public ProxyModelMergeDelegate(ModelMergeDelegate delegate) {
-        this.delegate = delegate;
-    }
-
-    @Override
-    public void dbEntityAdded(DbEntity ent) {
-        delegate.dbEntityAdded(ent);
-    }
-
-    @Override
-    public void dbEntityRemoved(DbEntity ent) {
-        delegate.dbEntityRemoved(ent);
-    }
-
-    @Override
-    public void objEntityAdded(ObjEntity ent) {
-        delegate.objEntityAdded(ent);
-    }
-
-    @Override
-    public void objEntityRemoved(ObjEntity ent) {
-        delegate.objEntityRemoved(ent);
-    }
-
-    @Override
-    public void dbAttributeAdded(DbAttribute att) {
-        delegate.dbAttributeAdded(att);
-    }
-
-    @Override
-    public void dbAttributeRemoved(DbAttribute att) {
-        delegate.dbAttributeRemoved(att);
-    }
-
-    @Override
-    public void dbAttributeModified(DbAttribute att) {
-        delegate.dbAttributeModified(att);
-    }
-
-    @Override
-    public void objAttributeAdded(ObjAttribute att) {
-        delegate.objAttributeAdded(att);
-    }
-
-    @Override
-    public void objAttributeRemoved(ObjAttribute att) {
-        delegate.objAttributeRemoved(att);
-    }
-
-    @Override
-    public void objAttributeModified(ObjAttribute att) {
-        delegate.objAttributeModified(att);
-    }
-
-    @Override
-    public void dbRelationshipAdded(DbRelationship rel) {
-        delegate.dbRelationshipAdded(rel);
-    }
-
-    @Override
-    public void dbRelationshipRemoved(DbRelationship rel) {
-        delegate.dbRelationshipRemoved(rel);
-    }
-
-    @Override
-    public void objRelationshipAdded(ObjRelationship rel) {
-        delegate.objRelationshipAdded(rel);
-    }
-
-    @Override
-    public void objRelationshipRemoved(ObjRelationship rel) {
-        delegate.objRelationshipRemoved(rel);
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/SetAllowNullToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetAllowNullToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetAllowNullToDb.java
deleted file mode 100644
index 07f677c..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetAllowNullToDb.java
+++ /dev/null
@@ -1,56 +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.merge;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-/**
- * A {@link MergerToken} to add a "allow null" clause to a column.
- * 
- */
-public class SetAllowNullToDb extends AbstractToDbToken.EntityAndColumn {
-
-    public SetAllowNullToDb(DbEntity entity, DbAttribute column) {
-        super("Set Allow Null", entity, column);
-    }
-
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        StringBuilder sqlBuffer = new StringBuilder();
-        QuotingStrategy context = adapter.getQuotingStrategy();
-        sqlBuffer.append("ALTER TABLE ");
-        sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-        sqlBuffer.append(" ALTER COLUMN ");
-        sqlBuffer.append(context.quotedName(getColumn()));
-        sqlBuffer.append(" DROP NOT NULL");
-
-        return Collections.singletonList(sqlBuffer.toString());
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createSetNotNullToModel(getEntity(), getColumn());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/SetAllowNullToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetAllowNullToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetAllowNullToModel.java
deleted file mode 100644
index 049240d..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetAllowNullToModel.java
+++ /dev/null
@@ -1,42 +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.merge;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-/**
- * A {@link MergerToken} to set the mandatory field of a {@link DbAttribute} to false
- * 
- */
-public class SetAllowNullToModel extends AbstractToModelToken.EntityAndColumn {
-
-    public SetAllowNullToModel(DbEntity entity, DbAttribute column) {
-        super("Set Allow Null", entity, column);
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createSetNotNullToDb(getEntity(), getColumn());
-    }
-
-    public void execute(MergerContext mergerContext) {
-        getColumn().setMandatory(false);
-        mergerContext.getModelMergeDelegate().dbAttributeModified(getColumn());
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index 466a9d9..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToDb.java
+++ /dev/null
@@ -1,119 +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.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;
-
-/**
- * An {@link MergerToken} to use to set type, length and precision.
- */
-public class SetColumnTypeToDb extends AbstractToDbToken.Entity {
-
-    private DbAttribute columnOriginal;
-    private DbAttribute columnNew;
-
-    public SetColumnTypeToDb(DbEntity entity, DbAttribute columnOriginal, DbAttribute columnNew) {
-        super("Set Column Type", entity);
-        this.columnOriginal = columnOriginal;
-        this.columnNew = columnNew;
-    }
-    
-    /**
-     * append the part of the token before the actual column data type
-     * @param context 
-     */
-    protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
-        sqlBuffer.append("ALTER TABLE ");
-        sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
-        sqlBuffer.append(" ALTER ");
-        sqlBuffer.append(context.quotedName(columnNew));
-        sqlBuffer.append(" TYPE ");
-    }
-
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        StringBuffer sqlBuffer = new StringBuffer();
-        appendPrefix(sqlBuffer, adapter.getQuotingStrategy());
-  
-        sqlBuffer.append(JdbcAdapter.getType(adapter, columnNew));
-        sqlBuffer.append(JdbcAdapter.sizeAndPrecision(adapter, columnNew));
-
-        return Collections.singletonList(sqlBuffer.toString());
-    }
-
-    @Override
-    public String getTokenValue() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(getEntity().getName());
-        sb.append(".");
-        sb.append(columnNew.getName());
-
-        if (columnOriginal.getType() != columnNew.getType()) {
-            sb.append(" type: ");
-            sb.append(TypesMapping.getSqlNameByType(columnOriginal.getType()));
-            sb.append(" -> ");
-            sb.append(TypesMapping.getSqlNameByType(columnNew.getType()));
-        }
-
-        if (columnOriginal.getMaxLength() != columnNew.getMaxLength()) {
-            sb.append(" maxLength: ");
-            sb.append(columnOriginal.getMaxLength());
-            sb.append(" -> ");
-            sb.append(columnNew.getMaxLength());
-        }
-
-        if (columnOriginal.getAttributePrecision() != columnNew.getAttributePrecision()) {
-            sb.append(" precision: ");
-            sb.append(columnOriginal.getAttributePrecision());
-            sb.append(" -> ");
-            sb.append(columnNew.getAttributePrecision());
-        }
-
-        if (columnOriginal.getScale() != columnNew.getScale()) {
-            sb.append(" scale: ");
-            sb.append(columnOriginal.getScale());
-            sb.append(" -> ");
-            sb.append(columnNew.getScale());
-        }
-
-        return sb.toString();
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createSetColumnTypeToModel(getEntity(), columnNew, columnOriginal);
-    }
-
-    public DbAttribute getColumnOriginal() {
-        return columnOriginal;
-    }
-
-    public DbAttribute getColumnNew() {
-        return columnNew;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToModel.java
deleted file mode 100644
index 784bc53..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetColumnTypeToModel.java
+++ /dev/null
@@ -1,101 +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.merge;
-
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-/**
- * A {@link MergerToken} that modifies one original {@link DbAttribute} to match another
- * new {@link DbAttribute}s type, maxLength and precision. The name and mandatory fields
- * are not modified by this token.
- * 
- */
-public class SetColumnTypeToModel extends AbstractToModelToken.Entity {
-
-    private DbAttribute columnOriginal;
-    private DbAttribute columnNew;
-
-    public SetColumnTypeToModel(DbEntity entity, DbAttribute columnOriginal,
-            DbAttribute columnNew) {
-        super("Set Column Type", entity);
-        this.columnOriginal = columnOriginal;
-        this.columnNew = columnNew;
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createSetColumnTypeToDb(getEntity(), columnNew, columnOriginal);
-    }
-
-    public void execute(MergerContext mergerContext) {
-        columnOriginal.setType(columnNew.getType());
-        columnOriginal.setMaxLength(columnNew.getMaxLength());
-        columnOriginal.setAttributePrecision(columnNew.getAttributePrecision());
-        columnOriginal.setScale(columnNew.getScale());
-        mergerContext.getModelMergeDelegate().dbAttributeModified(columnOriginal);
-    }
-
-    @Override
-    public String getTokenValue() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(getEntity().getName());
-        sb.append(".");
-        sb.append(columnNew.getName());
-
-        if (columnOriginal.getType() != columnNew.getType()) {
-            sb.append(" type: ");
-            sb.append(TypesMapping.getSqlNameByType(columnOriginal.getType()));
-            sb.append(" -> ");
-            sb.append(TypesMapping.getSqlNameByType(columnNew.getType()));
-        }
-
-        if (columnOriginal.getMaxLength() != columnNew.getMaxLength()) {
-            sb.append(" maxLength: ");
-            sb.append(columnOriginal.getMaxLength());
-            sb.append(" -> ");
-            sb.append(columnNew.getMaxLength());
-        }
-
-        if (columnOriginal.getAttributePrecision() != columnNew.getAttributePrecision()) {
-            sb.append(" precision: ");
-            sb.append(columnOriginal.getAttributePrecision());
-            sb.append(" -> ");
-            sb.append(columnNew.getAttributePrecision());
-        }
-
-        if (columnOriginal.getScale() != columnNew.getScale()) {
-            sb.append(" scale: ");
-            sb.append(columnOriginal.getScale());
-            sb.append(" -> ");
-            sb.append(columnNew.getScale());
-        }
-
-        return sb.toString();
-    }
-    
-    public DbAttribute getColumnOriginal() {
-        return columnOriginal;
-    }
-
-    public DbAttribute getColumnNew() {
-        return columnNew;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/SetNotNullToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetNotNullToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetNotNullToDb.java
deleted file mode 100644
index 60b5ad5..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetNotNullToDb.java
+++ /dev/null
@@ -1,50 +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.merge;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-/**
- * A {@link MergerToken} to add a "not null" clause to a column.
- */
-public class SetNotNullToDb extends AbstractToDbToken.EntityAndColumn {
-
-    public SetNotNullToDb(DbEntity entity, DbAttribute column) {
-        super("Set Not Null", entity, column);
-    }
-
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        QuotingStrategy context = adapter.getQuotingStrategy();
-
-        return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
-                + " ALTER COLUMN " + context.quotedName(getColumn()) + " SET NOT NULL");
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createSetAllowNullToModel(getEntity(), getColumn());
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/SetNotNullToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetNotNullToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetNotNullToModel.java
deleted file mode 100644
index 767f9e5..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetNotNullToModel.java
+++ /dev/null
@@ -1,42 +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.merge;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-/**
- * A {@link MergerToken} to set the mandatory field of a {@link DbAttribute} to true
- * 
- */
-public class SetNotNullToModel extends AbstractToModelToken.EntityAndColumn {
-
-    public SetNotNullToModel(DbEntity entity, DbAttribute column) {
-        super("Set Not Null", entity, column);
-    }
-    
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createSetAllowNullToDb(getEntity(), getColumn());
-    }
-
-    public void execute(MergerContext mergerContext) {
-        getColumn().setMandatory(true);
-        mergerContext.getModelMergeDelegate().dbAttributeModified(getColumn());
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/SetPrimaryKeyToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetPrimaryKeyToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetPrimaryKeyToDb.java
deleted file mode 100644
index 8f467fa..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetPrimaryKeyToDb.java
+++ /dev/null
@@ -1,85 +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.merge;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.QuotingStrategy;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-public class SetPrimaryKeyToDb extends AbstractToDbToken.Entity {
-
-    private Collection<DbAttribute> primaryKeyOriginal;
-    private Collection<DbAttribute> primaryKeyNew;
-    private String detectedPrimaryKeyName;
-
-    public SetPrimaryKeyToDb(DbEntity entity, Collection<DbAttribute> primaryKeyOriginal,
-            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
-        super("Set Primary Key", entity);
-
-        this.primaryKeyOriginal = primaryKeyOriginal;
-        this.primaryKeyNew = primaryKeyNew;
-        this.detectedPrimaryKeyName = detectedPrimaryKeyName;
-    }
-
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        List<String> sqls = new ArrayList<String>();
-        if (!primaryKeyOriginal.isEmpty()) {
-            appendDropOriginalPrimaryKeySQL(adapter, sqls);
-        }
-        appendAddNewPrimaryKeySQL(adapter, sqls);
-        return sqls;
-    }
-
-    protected void appendDropOriginalPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
-        if (detectedPrimaryKeyName == null) {
-            return;
-        }
-        sqls.add("ALTER TABLE " + adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity())
-                + " DROP CONSTRAINT " + detectedPrimaryKeyName);
-    }
-
-    protected void appendAddNewPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
-        QuotingStrategy quotingStrategy = adapter.getQuotingStrategy();
-
-        StringBuilder sql = new StringBuilder();
-        sql.append("ALTER TABLE ");
-        sql.append(quotingStrategy.quotedFullyQualifiedName(getEntity()));
-        sql.append(" ADD PRIMARY KEY (");
-        for (Iterator<DbAttribute> it = primaryKeyNew.iterator(); it.hasNext();) {
-            sql.append(quotingStrategy.quotedName(it.next()));
-            if (it.hasNext()) {
-                sql.append(", ");
-            }
-        }
-        sql.append(")");
-        sqls.add(sql.toString());
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createSetPrimaryKeyToModel(getEntity(), primaryKeyNew, primaryKeyOriginal,
-                detectedPrimaryKeyName);
-    }
-}


[12/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoader.java
new file mode 100644
index 0000000..a6f286a
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoader.java
@@ -0,0 +1,829 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.dbsync.reverse.filters.CatalogFilter;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.SchemaFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.DbRelationshipDetected;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.ProcedureParameter;
+import org.apache.cayenne.map.naming.DefaultUniqueNameGenerator;
+import org.apache.cayenne.map.naming.ExportedKey;
+import org.apache.cayenne.map.naming.LegacyNameGenerator;
+import org.apache.cayenne.map.naming.NameCheckers;
+import org.apache.cayenne.map.naming.ObjectNameGenerator;
+import org.apache.cayenne.dbsync.merge.EntityMergeSupport;
+import org.apache.cayenne.util.EqualsBuilder;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Performs reverse engineering of the database. It can create
+ * DataMaps using database meta data obtained via JDBC driver.
+ *
+ * @since 4.0
+ */
+public class DbLoader {
+
+	private static final Log LOGGER = LogFactory.getLog(DbLoader.class);
+
+	public static final String WILDCARD = "%";
+	public static final String WILDCARD_PATTERN = ".*";
+
+	private final Connection connection;
+	private final DbAdapter adapter;
+	private final DbLoaderDelegate delegate;
+
+	private boolean creatingMeaningfulPK;
+
+	private DatabaseMetaData metaData;
+
+	/**
+	 * Strategy for choosing names for entities, attributes and relationships
+	 */
+	private ObjectNameGenerator nameGenerator;
+
+	/**
+	 * Creates new DbLoader.
+	 */
+	public DbLoader(Connection connection, DbAdapter adapter, DbLoaderDelegate delegate) {
+		this(connection, adapter, delegate, new LegacyNameGenerator());
+	}
+
+	/**
+	 * Creates new DbLoader with specified naming strategy.
+	 *
+	 * @since 3.0
+	 */
+	public DbLoader(Connection connection, DbAdapter adapter, DbLoaderDelegate delegate, ObjectNameGenerator strategy) {
+		this.adapter = adapter;
+		this.connection = connection;
+		this.delegate = delegate == null ? new DefaultDbLoaderDelegate() : delegate;
+
+		setNameGenerator(strategy);
+	}
+
+	/**
+	 * Returns DatabaseMetaData object associated with this DbLoader.
+	 */
+	private DatabaseMetaData getMetaData() throws SQLException {
+		if (metaData == null) {
+			metaData = connection.getMetaData();
+		}
+		return metaData;
+	}
+
+	/**
+	 * Check if database support schemas.
+	 */
+	protected boolean supportSchemas() throws SQLException {
+		if (metaData == null) {
+			metaData = connection.getMetaData();
+		}
+		return metaData.supportsSchemasInTableDefinitions();
+	}
+
+	/**
+	 * Check if database support catalogs.
+	 */
+	protected boolean supportCatalogs() throws SQLException {
+		if (metaData == null) {
+			metaData = connection.getMetaData();
+		}
+		return metaData.supportsCatalogsInTableDefinitions();
+	}
+
+	/**
+	 * @since 3.0
+	 */
+	public void setCreatingMeaningfulPK(boolean creatingMeaningfulPK) {
+		this.creatingMeaningfulPK = creatingMeaningfulPK;
+	}
+
+	/**
+	 * Returns true if the generator should map all primary key columns as
+	 * ObjAttributes.
+	 *
+	 * @since 3.0
+	 */
+	public boolean isCreatingMeaningfulPK() {
+		return creatingMeaningfulPK;
+	}
+
+	/**
+	 * Returns database connection used by this DbLoader.
+	 *
+	 * @since 3.0
+	 */
+	public Connection getConnection() {
+		return connection;
+	}
+
+	/**
+	 * Returns DbAdapter associated with this DbLoader.
+	 *
+	 * @since 1.1
+	 */
+	public DbAdapter getAdapter() {
+		return adapter;
+	}
+
+	/**
+	 * Retrieves catalogs for the database associated with this DbLoader.
+	 *
+	 * @return List with the catalog names, empty Array if none found.
+	 */
+	public List<String> getCatalogs() throws SQLException {
+		try (ResultSet rs = getMetaData().getCatalogs()) {
+			return getStrings(rs);
+		}
+	}
+
+	/**
+	 * Retrieves the schemas for the database.
+	 *
+	 * @return List with the schema names, empty Array if none found.
+	 */
+	public List<String> getSchemas() throws SQLException {
+
+		try (ResultSet rs = getMetaData().getSchemas()) {
+			return getStrings(rs);
+		}
+	}
+
+	private static List<String> getStrings(ResultSet rs) throws SQLException {
+		List<String> strings = new ArrayList<String>();
+
+		while (rs.next()) {
+			strings.add(rs.getString(1));
+		}
+
+		return strings;
+	}
+
+	/**
+	 * Returns all the table types for the given database. Types may be such as
+	 * Typical types are "TABLE", "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY",
+	 * "LOCAL TEMPORARY", "ALIAS", "SYNONYM"., etc.
+	 *
+	 * @return List of Strings, empty array if nothing found.
+	 */
+	public List<String> getTableTypes() throws SQLException {
+		List<String> types = new ArrayList<String>();
+
+		try (ResultSet rs = getMetaData().getTableTypes();) {
+			while (rs.next()) {
+				types.add(rs.getString("TABLE_TYPE").trim());
+			}
+		}
+
+		return types;
+	}
+
+	/**
+	 * Creates an ObjEntity for each DbEntity in the map.
+	 */
+	public Collection<ObjEntity> loadObjEntities(DataMap map, DbLoaderConfiguration config,
+			Collection<DbEntity> entities) {
+		Collection<ObjEntity> loadedEntities = DbLoader.loadObjEntities(map, config, entities, nameGenerator);
+
+		createEntityMerger(map).synchronizeWithDbEntities(loadedEntities);
+
+		return loadedEntities;
+	}
+
+	public static Collection<ObjEntity> loadObjEntities(DataMap map, DbLoaderConfiguration config,
+			Collection<DbEntity> entities, ObjectNameGenerator nameGenerator) {
+		if (entities.isEmpty()) {
+			return Collections.emptyList();
+		}
+
+		Collection<ObjEntity> loadedEntities = new ArrayList<ObjEntity>(entities.size());
+
+		// doLoad empty ObjEntities for all the tables
+		for (DbEntity dbEntity : entities) {
+
+			// check if there are existing entities
+
+			// TODO: performance. This is an O(n^2) search and it shows on
+			// YourKit profiles. Pre-cache mapped entities perhaps (?)
+			Collection<ObjEntity> existing = map.getMappedEntities(dbEntity);
+			if (!existing.isEmpty()) {
+				loadedEntities.addAll(existing);
+				continue;
+			}
+
+			String objEntityName = DefaultUniqueNameGenerator.generate(NameCheckers.objEntity, map,
+					nameGenerator.createObjEntityName(dbEntity));
+
+			ObjEntity objEntity = new ObjEntity(objEntityName);
+			objEntity.setDbEntity(dbEntity);
+			objEntity.setClassName(config.getGenericClassName() != null ? config.getGenericClassName() : map
+					.getNameWithDefaultPackage(objEntity.getName()));
+
+			map.addObjEntity(objEntity);
+			loadedEntities.add(objEntity);
+		}
+
+		return loadedEntities;
+	}
+
+	/**
+	 * @since 4.0
+	 */
+	protected EntityMergeSupport createEntityMerger(DataMap map) {
+		return new EntityMergeSupport(map, nameGenerator, !creatingMeaningfulPK);
+	}
+
+	protected void loadDbRelationships(DbLoaderConfiguration config, String catalog, String schema,
+			List<DbEntity> tables) throws SQLException {
+		if (config.isSkipRelationshipsLoading()) {
+			return;
+		}
+
+		// Get all the foreign keys referencing this table
+		Map<String, DbEntity> tablesMap = new HashMap<>();
+		for (DbEntity table : tables) {
+			tablesMap.put(table.getName(), table);
+		}
+
+		Map<String, Set<ExportedKey>> keys = loadExportedKeys(config, catalog, schema, tablesMap);
+		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 = tablesMap.get(key.getPKTableName());
+			if (pkEntity == null) {
+				skipRelationLog(key, key.getPKTableName());
+				continue;
+			}
+
+			DbEntity fkEntity = tablesMap.get(key.getFKTableName());
+			if (fkEntity == null) {
+				skipRelationLog(key, key.getFKTableName());
+				continue;
+			}
+
+			if (!new EqualsBuilder().append(pkEntity.getCatalog(), key.pkCatalog)
+					.append(pkEntity.getSchema(), key.pkSchema).append(fkEntity.getCatalog(), key.fkCatalog)
+					.append(fkEntity.getSchema(), key.fkSchema).isEquals()) {
+
+				LOGGER.info("Skip relation: '" + key + "' because it related to objects from other catalog/schema");
+				LOGGER.info("     relation primary key: '" + key.pkCatalog + "." + key.pkSchema + "'");
+				LOGGER.info("       primary key entity: '" + pkEntity.getCatalog() + "." + pkEntity.getSchema() + "'");
+				LOGGER.info("     relation foreign key: '" + key.fkCatalog + "." + key.fkSchema + "'");
+				LOGGER.info("       foreign key entity: '" + fkEntity.getCatalog() + "." + fkEntity.getSchema() + "'");
+				continue;
+			}
+
+			// forwardRelationship is a reference from table with primary key
+			DbRelationship forwardRelationship = new DbRelationship(generateName(pkEntity, key, true));
+			forwardRelationship.setSourceEntity(pkEntity);
+			forwardRelationship.setTargetEntityName(fkEntity);
+
+			// forwardRelationship is a reference from table with foreign key,
+			// it is what exactly we load from db
+			DbRelationshipDetected reverseRelationship = new DbRelationshipDetected(generateName(fkEntity, key, false));
+			reverseRelationship.setFkName(key.getFKName());
+			reverseRelationship.setSourceEntity(fkEntity);
+			reverseRelationship.setTargetEntityName(pkEntity);
+			reverseRelationship.setToMany(false);
+
+			createAndAppendJoins(exportedKeys, pkEntity, fkEntity, forwardRelationship, reverseRelationship);
+
+			boolean toDependentPK = isToDependentPK(forwardRelationship);
+			forwardRelationship.setToDependentPK(toDependentPK);
+
+			boolean isOneToOne = toDependentPK
+					&& fkEntity.getPrimaryKeys().size() == forwardRelationship.getJoins().size();
+
+			forwardRelationship.setToMany(!isOneToOne);
+			forwardRelationship.setName(generateName(pkEntity, key, !isOneToOne));
+
+			if (delegate.dbRelationshipLoaded(fkEntity, reverseRelationship)) {
+				fkEntity.addRelationship(reverseRelationship);
+			}
+			if (delegate.dbRelationshipLoaded(pkEntity, forwardRelationship)) {
+				pkEntity.addRelationship(forwardRelationship);
+			}
+		}
+	}
+
+	private boolean isToDependentPK(DbRelationship forwardRelationship) {
+		for (DbJoin dbJoin : forwardRelationship.getJoins()) {
+			if (!dbJoin.getTarget().isPrimaryKey()) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	private void createAndAppendJoins(Set<ExportedKey> exportedKeys, DbEntity pkEntity, DbEntity fkEntity,
+			DbRelationship forwardRelationship, DbRelationshipDetected reverseRelationship) {
+		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;
+			}
+
+			DbAttribute fkAtt = fkEntity.getAttribute(fkName);
+			if (fkAtt == null) {
+				LOGGER.info("no attribute for declared foreign key: " + fkName);
+				continue;
+			}
+
+			forwardRelationship.addJoin(new DbJoin(forwardRelationship, pkName, fkName));
+			reverseRelationship.addJoin(new DbJoin(reverseRelationship, fkName, pkName));
+		}
+	}
+
+	private Map<String, Set<ExportedKey>> loadExportedKeys(DbLoaderConfiguration config, String catalog, String schema,
+			Map<String, DbEntity> tables) throws SQLException {
+		Map<String, Set<ExportedKey>> keys = new HashMap<>();
+
+		for (DbEntity dbEntity : tables.values()) {
+			if (!delegate.dbRelationship(dbEntity)) {
+				continue;
+			}
+
+			ResultSet rs;
+			try {
+				rs = getMetaData().getExportedKeys(catalog, schema, dbEntity.getName());
+			} catch (SQLException cay182Ex) {
+				// Sybase-specific - the line above blows on VIEWS, see CAY-182.
+				LOGGER.info(
+						"Error getting relationships for '" + catalog + "." + schema + "', ignoring. "
+								+ cay182Ex.getMessage(), cay182Ex);
+				return new HashMap<>();
+			}
+
+			try {
+				while (rs.next()) {
+					ExportedKey key = ExportedKey.extractData(rs);
+
+					DbEntity fkEntity = tables.get(key.getFKTableName());
+					if (fkEntity == null) {
+						skipRelationLog(key, key.getFKTableName());
+						continue;
+					}
+
+					if (config.getFiltersConfig().tableFilter(fkEntity.getCatalog(), fkEntity.getSchema())
+							.isIncludeTable(fkEntity.getName()) == null) {
+						continue;
+					}
+
+					Set<ExportedKey> exportedKeys = keys.get(key.getStrKey());
+					if (exportedKeys == null) {
+						exportedKeys = new TreeSet<ExportedKey>();
+
+						keys.put(key.getStrKey(), exportedKeys);
+					}
+					exportedKeys.add(key);
+				}
+
+			} 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) {
+		String forwardPreferredName = nameGenerator.createDbRelationshipName(key, toMany);
+		return DefaultUniqueNameGenerator.generate(NameCheckers.dbRelationship, entity, forwardPreferredName);
+	}
+
+	/**
+	 * Flattens many-to-many relationships in the generated model.
+	 */
+	public static void flattenManyToManyRelationships(DataMap map, Collection<ObjEntity> loadedObjEntities,
+			ObjectNameGenerator objectNameGenerator) {
+		if (loadedObjEntities.isEmpty()) {
+			return;
+		}
+		Collection<ObjEntity> entitiesForDelete = new LinkedList<ObjEntity>();
+
+		for (ObjEntity curEntity : loadedObjEntities) {
+			ManyToManyCandidateEntity entity = ManyToManyCandidateEntity.build(curEntity);
+
+			if (entity != null) {
+				entity.optimizeRelationships(objectNameGenerator);
+				entitiesForDelete.add(curEntity);
+			}
+		}
+
+		// remove needed entities
+		for (ObjEntity curDeleteEntity : entitiesForDelete) {
+			map.removeObjEntity(curDeleteEntity.getName(), true);
+		}
+		loadedObjEntities.removeAll(entitiesForDelete);
+	}
+
+	private void fireObjEntitiesAddedEvents(Collection<ObjEntity> loadedObjEntities) {
+		for (ObjEntity curEntity : loadedObjEntities) {
+			// notify delegate
+			if (delegate != null) {
+				delegate.objEntityAdded(curEntity);
+			}
+		}
+	}
+
+	/**
+	 * By default we want to load Tables and Views for mo types
+	 *
+	 * @see DbLoader#getTableTypes()
+	 * @since 4.0
+	 */
+	public String[] getDefaultTableTypes() {
+		List<String> list = new ArrayList<String>(2);
+
+		String viewType = adapter.tableTypeForView();
+		if (viewType != null) {
+			list.add(viewType);
+		}
+
+		String tableType = adapter.tableTypeForTable();
+		if (tableType != null) {
+			list.add(tableType);
+		}
+
+		return list.toArray(new String[list.size()]);
+	}
+
+	/**
+	 * Performs database reverse engineering and generates DataMap that contains
+	 * default mapping of the tables and views. By default will include regular
+	 * tables and views.
+	 *
+	 * @since 1.0.7
+	 * @deprecated since 4.0 use
+	 *             {@link #load(DataMap, DbLoaderConfiguration)}
+	 *             method that supports catalogs.
+	 */
+	@Deprecated
+	public DataMap loadDataMapFromDB(String schemaPattern, String tablePattern, DataMap dataMap) throws SQLException {
+
+		DbLoaderConfiguration configuration = new DbLoaderConfiguration();
+		configuration.setFiltersConfig(FiltersConfig.create(null, schemaPattern, TableFilter.include(tablePattern),
+				PatternFilter.INCLUDE_NOTHING));
+
+		load(dataMap, configuration);
+		return dataMap;
+	}
+
+	/**
+	 * Performs database reverse engineering and generates DataMap object that
+	 * contains default mapping of the tables and views. Allows to limit types
+	 * of tables to read.
+	 *
+	 * @deprecated since 4.0 use
+	 *             {@link #load(DataMap, DbLoaderConfiguration)}
+	 *             method that supports catalogs.
+	 */
+	@Deprecated
+	public DataMap loadDataMapFromDB(String schemaPattern, String tablePattern, String[] tableTypes, DataMap dataMap)
+			throws SQLException {
+		dataMap.clear();
+
+		DbLoaderConfiguration config = new DbLoaderConfiguration();
+		config.setFiltersConfig(FiltersConfig.create(null, schemaPattern, TableFilter.include(tablePattern),
+				PatternFilter.INCLUDE_NOTHING));
+		config.setTableTypes(tableTypes);
+
+		load(dataMap, config);
+		return dataMap;
+	}
+
+	/**
+	 * Performs database reverse engineering based on the specified config and
+	 * fills the specified DataMap object with DB and object mapping info.
+	 *
+	 * @since 4.0
+	 */
+	public void load(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+		LOGGER.info("Schema loading...");
+
+		String[] types = config.getTableTypes();
+		if (types == null || types.length == 0) {
+			types = getDefaultTableTypes();
+		}
+
+		for (CatalogFilter catalog : config.getFiltersConfig().catalogs) {
+			for (SchemaFilter schema : catalog.schemas) {
+
+				List<DbEntity> entities = createTableLoader(catalog.name, schema.name, schema.tables).loadDbEntities(
+						dataMap, config, types);
+
+				if (entities != null) {
+					loadDbRelationships(config, catalog.name, schema.name, entities);
+
+					prepareObjLayer(dataMap, config, entities);
+				}
+			}
+		}
+	}
+
+	protected DbTableLoader createTableLoader(String catalog, String schema, TableFilter filter) throws SQLException {
+		return new DbTableLoader(catalog, schema, getMetaData(), delegate, new DbAttributesPerSchemaLoader(catalog,
+				schema, getMetaData(), adapter, filter));
+	}
+
+	public void prepareObjLayer(DataMap dataMap, DbLoaderConfiguration config, Collection<DbEntity> entities) {
+		Collection<ObjEntity> loadedObjEntities = loadObjEntities(dataMap, config, entities);
+		flattenManyToManyRelationships(dataMap, loadedObjEntities, getNameGenerator());
+		fireObjEntitiesAddedEvents(loadedObjEntities);
+	}
+
+	/**
+	 * 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 4.0
+	 */
+	public DataMap load(DbLoaderConfiguration config) throws SQLException {
+
+		DataMap dataMap = new DataMap();
+		load(dataMap, config);
+		loadProcedures(dataMap, config);
+
+		return dataMap;
+	}
+
+	/**
+	 * Loads database stored procedures into the DataMap.
+	 * <p>
+	 * <i>As of 1.1 there is no boolean property or delegate method to make
+	 * procedure loading optional or to implement custom merging logic, so
+	 * currently this method is NOT CALLED from "loadDataMapFromDB" and should
+	 * be invoked explicitly by the user. </i>
+	 * </p>
+	 *
+	 * @since 1.1
+	 * @deprecated since 4.0 use loadProcedures(DataMap, String, String, String)
+	 *             that supports "catalog" pattern.
+	 */
+	@Deprecated
+	public void loadProceduresFromDB(String schemaPattern, String namePattern, DataMap dataMap) throws SQLException {
+		DbLoaderConfiguration configuration = new DbLoaderConfiguration();
+		configuration.setFiltersConfig(FiltersConfig.create(null, schemaPattern, TableFilter.everything(),
+				new PatternFilter().include(namePattern)));
+
+		loadProcedures(dataMap, configuration);
+	}
+
+	/**
+	 * Loads database stored procedures into the DataMap.
+	 * <p>
+	 * <i>As of 1.1 there is no boolean property or delegate method to make
+	 * procedure loading optional or to implement custom merging logic, so
+	 * currently this method is NOT CALLED from "loadDataMapFromDB" and should
+	 * be invoked explicitly by the user. </i>
+	 * </p>
+	 *
+	 * @since 4.0
+	 */
+	public Map<String, Procedure> loadProcedures(DataMap dataMap, DbLoaderConfiguration config) throws SQLException {
+
+		Map<String, Procedure> procedures = loadProcedures(config);
+		if (procedures.isEmpty()) {
+			return procedures;
+		}
+
+		loadProceduresColumns(config, procedures);
+
+		for (Procedure procedure : procedures.values()) {
+			dataMap.addProcedure(procedure);
+		}
+
+		return procedures;
+	}
+
+	private void loadProceduresColumns(DbLoaderConfiguration config, Map<String, Procedure> procedures)
+			throws SQLException {
+
+		for (CatalogFilter catalog : config.getFiltersConfig().catalogs) {
+			for (SchemaFilter schema : catalog.schemas) {
+				loadProceduresColumns(procedures, catalog.name, schema.name);
+			}
+		}
+	}
+
+	private void loadProceduresColumns(Map<String, Procedure> procedures, String catalog, String schema)
+			throws SQLException {
+
+		try (ResultSet columnsRS = getMetaData().getProcedureColumns(catalog, schema, null, null);) {
+			while (columnsRS.next()) {
+
+				String s = columnsRS.getString("PROCEDURE_SCHEM");
+				String name = columnsRS.getString("PROCEDURE_NAME");
+				String key = (s == null ? "" : s + '.') + name;
+				Procedure procedure = procedures.get(key);
+				if (procedure == null) {
+					continue;
+				}
+
+				ProcedureParameter column = loadProcedureParams(columnsRS, key, procedure);
+				if (column == null) {
+					continue;
+				}
+				procedure.addCallParameter(column);
+			}
+		}
+	}
+
+	private ProcedureParameter loadProcedureParams(ResultSet columnsRS, String key, Procedure procedure)
+			throws SQLException {
+		String columnName = columnsRS.getString("COLUMN_NAME");
+
+		// 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);
+		}
+
+		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;
+			}
+		}
+
+		int columnType = columnsRS.getInt("DATA_TYPE");
+
+		// ignore precision of non-decimal columns
+		int decimalDigits = -1;
+		if (TypesMapping.isDecimal(columnType)) {
+			decimalDigits = columnsRS.getShort("SCALE");
+			if (columnsRS.wasNull()) {
+				decimalDigits = -1;
+			}
+		}
+
+		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) {
+		switch (type) {
+		case DatabaseMetaData.procedureColumnIn:
+			return ProcedureParameter.IN_PARAMETER;
+		case DatabaseMetaData.procedureColumnInOut:
+			return ProcedureParameter.IN_OUT_PARAMETER;
+		case DatabaseMetaData.procedureColumnOut:
+			return ProcedureParameter.OUT_PARAMETER;
+		default:
+			return -1;
+		}
+	}
+
+	private Map<String, Procedure> loadProcedures(DbLoaderConfiguration config) throws SQLException {
+		Map<String, Procedure> procedures = new HashMap<>();
+
+		FiltersConfig filters = config.getFiltersConfig();
+		for (CatalogFilter catalog : filters.catalogs) {
+			for (SchemaFilter schema : catalog.schemas) {
+				if (filters.proceduresFilter(catalog.name, schema.name).isEmpty()) {
+					continue;
+				}
+
+				procedures.putAll(loadProcedures(filters, catalog.name, schema.name));
+			}
+		}
+
+		return procedures;
+	}
+
+	private Map<String, Procedure> loadProcedures(FiltersConfig filters, String catalog, String schema)
+			throws SQLException {
+		Map<String, Procedure> procedures = new HashMap<>();
+		// get procedures
+
+		try (ResultSet rs = getMetaData().getProcedures(catalog, schema, WILDCARD);) {
+			while (rs.next()) {
+
+				String name = rs.getString("PROCEDURE_NAME");
+				Procedure procedure = new Procedure(name);
+				procedure.setCatalog(rs.getString("PROCEDURE_CAT"));
+				procedure.setSchema(rs.getString("PROCEDURE_SCHEM"));
+
+				if (!filters.proceduresFilter(procedure.getCatalog(), procedure.getSchema()).isInclude(
+						procedure.getName())) {
+					LOGGER.info("skipping Cayenne PK procedure: " + name);
+					continue;
+				}
+
+				switch (rs.getShort("PROCEDURE_TYPE")) {
+				case DatabaseMetaData.procedureNoResult:
+				case DatabaseMetaData.procedureResultUnknown:
+					procedure.setReturningValue(false);
+					break;
+				case DatabaseMetaData.procedureReturnsResult:
+					procedure.setReturningValue(true);
+					break;
+				}
+
+				procedures.put(procedure.getFullyQualifiedName(), procedure);
+			}
+		}
+		return procedures;
+	}
+
+	/**
+	 * Sets new naming strategy for reverse engineering
+	 *
+	 * @since 3.0
+	 */
+	public void setNameGenerator(ObjectNameGenerator strategy) {
+		if (strategy == null) {
+			LOGGER.warn("Attempt to set null into NameGenerator. LegacyNameGenerator will be used.");
+			this.nameGenerator = new LegacyNameGenerator();
+		} else {
+			this.nameGenerator = strategy;
+		}
+	}
+
+	/**
+	 * @return naming strategy for reverse engineering
+	 * @since 3.0
+	 */
+	public ObjectNameGenerator getNameGenerator() {
+		return nameGenerator;
+	}
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoaderConfiguration.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoaderConfiguration.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoaderConfiguration.java
new file mode 100644
index 0000000..e5421fe
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoaderConfiguration.java
@@ -0,0 +1,150 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+
+/**
+ * @since 4.0
+ */
+public class DbLoaderConfiguration {
+
+    /**
+     * Returns a name of a generic class that should be used for all
+     * ObjEntities. The most common generic class is
+     * {@link org.apache.cayenne.CayenneDataObject}. If generic class name is
+     * null (which is the default), DbLoader will assign each entity a unique
+     * class name derived from the table name.
+     *
+     */
+    private String genericClassName;
+
+/*
+    // TODO: Andrus, 10/29/2005 - this type of filtering should be delegated to adapter
+       TODO by default should skip name.startsWith("BIN$")
+
+    private NameFilter tableFilter = NamePatternMatcher.build(null, null, "BIN$");
+
+    private NameFilter columnFilter;
+
+    private NameFilter proceduresFilter = new NameFilter() {
+        private final Collection<String> excludedProcedures = Arrays.asList(
+                "auto_pk_for_table",
+                "auto_pk_for_table;1" // the last name is some Mac OS X Sybase artifact
+        );
+
+        @Override
+        public boolean isIncluded(String string) {
+            return !excludedProcedures.contains(string);
+        }
+    };
+*/
+
+
+    /**
+     * Java class implementing org.apache.cayenne.map.naming.NamingStrategy.
+     * This is used to specify how ObjEntities will be mapped from the imported
+     * DB schema.
+     */
+    private String namingStrategy;
+
+    private Boolean skipRelationshipsLoading;
+
+    private Boolean skipPrimaryKeyLoading;
+
+    private String[] tableTypes;
+
+    private FiltersConfig filtersConfig;
+
+    public String getGenericClassName() {
+        return genericClassName;
+    }
+
+    public void setGenericClassName(String genericClassName) {
+        this.genericClassName = genericClassName;
+    }
+
+    public String[] getTableTypes() {
+        return tableTypes;
+    }
+
+    public void setTableTypes(String[] tableTypes) {
+        this.tableTypes = tableTypes;
+    }
+
+    public String getNamingStrategy() {
+        return namingStrategy;
+    }
+
+    public void setNamingStrategy(String namingStrategy) {
+        this.namingStrategy = namingStrategy;
+    }
+
+    public FiltersConfig getFiltersConfig() {
+        if (filtersConfig == null) {
+            // this case is used often in tests where config not initialized properly
+            return FiltersConfig.create(null, null, TableFilter.everything(), PatternFilter.INCLUDE_NOTHING);
+        }
+        return filtersConfig;
+    }
+
+    public void setFiltersConfig(FiltersConfig filtersConfig) {
+        this.filtersConfig = filtersConfig;
+    }
+
+    public boolean isSkipRelationshipsLoading() {
+        return skipRelationshipsLoading != null && skipRelationshipsLoading;
+    }
+
+    public Boolean getSkipRelationshipsLoading() {
+        return skipRelationshipsLoading;
+    }
+
+    public void setSkipRelationshipsLoading(Boolean skipRelationshipsLoading) {
+        this.skipRelationshipsLoading = skipRelationshipsLoading;
+    }
+
+    public void setSkipPrimaryKeyLoading(Boolean skipPrimaryKeyLoading) {
+        this.skipPrimaryKeyLoading = skipPrimaryKeyLoading;
+    }
+
+    public boolean getSkipPrimaryKeyLoading() {
+        return skipPrimaryKeyLoading;
+    }
+
+    public boolean isSkipPrimaryKeyLoading() {
+        return skipPrimaryKeyLoading != null && skipPrimaryKeyLoading;
+    }
+
+    @Override
+    public String toString() {
+        String res = "EntitiesFilters: " + getFiltersConfig();
+        if (isSkipRelationshipsLoading()) {
+            res += "\n Skip Loading Relationships! \n";
+        }
+
+        if (isSkipPrimaryKeyLoading()) {
+            res += "\n Skip Loading PrimaryKeys! \n";
+        }
+
+        return res;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoaderDelegate.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoaderDelegate.java
new file mode 100644
index 0000000..35a61fb
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbLoaderDelegate.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * DbLoaderDelegate defines API that allows to control the behavior of DbLoader
+ * during the database reverse-engineering. Delegate is also notified of the
+ * progress of reverse-engineering.
+ */
+public interface DbLoaderDelegate {
+
+    void dbEntityAdded(DbEntity entity);
+
+    void dbEntityRemoved(DbEntity entity);
+
+    /**
+     * Called before relationship loading for db-entity
+     * @param entity
+     *
+     * @return true in case you want process relationships for this entity
+     *         false otherwise
+     */
+    boolean dbRelationship(DbEntity entity);
+
+    /**
+     * Called before relationship will be added into db-entity but after it was loaded from db
+     * @param entity
+     *
+     * @return true in case you want add this relationship into entity
+     *         false otherwise
+     */
+    boolean dbRelationshipLoaded(DbEntity entity, DbRelationship relationship);
+
+    void objEntityAdded(ObjEntity entity);
+
+    void objEntityRemoved(ObjEntity entity);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbTableLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbTableLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbTableLoader.java
new file mode 100644
index 0000000..0d12e64
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbTableLoader.java
@@ -0,0 +1,195 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DetectedDbEntity;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * @since 4.0
+ */
+public class DbTableLoader {
+
+	private static final Log LOGGER = LogFactory.getLog(DbTableLoader.class);
+
+	private static final String WILDCARD = "%";
+
+	private final String catalog;
+	private final String schema;
+
+	private final DatabaseMetaData metaData;
+	private final DbLoaderDelegate delegate;
+
+	private final DbAttributesLoader attributesLoader;
+
+	public DbTableLoader(String catalog, String schema, DatabaseMetaData metaData, DbLoaderDelegate delegate,
+			DbAttributesLoader attributesLoader) {
+		this.catalog = catalog;
+		this.schema = schema;
+		this.metaData = metaData;
+		this.delegate = delegate;
+
+		this.attributesLoader = attributesLoader;
+	}
+
+	/**
+	 * Returns all tables for given combination of the criteria. Tables returned
+	 * as DbEntities without any attributes or relationships.
+	 *
+	 * @param types
+	 *            The types of table names to retrieve, null returns all types.
+	 * @return
+	 * @since 4.0
+	 */
+	public List<DetectedDbEntity> getDbEntities(TableFilter filters, String[] types) throws SQLException {
+		if (LOGGER.isDebugEnabled()) {
+			LOGGER.debug("Read tables: catalog=" + catalog + ", schema=" + schema + ", types=" + Arrays.toString(types));
+		}
+
+		List<DetectedDbEntity> tables = new LinkedList<DetectedDbEntity>();
+		try (ResultSet rs = metaData.getTables(catalog, schema, WILDCARD, types);) {
+			while (rs.next()) {
+				// Oracle 9i and newer has a nifty recycle bin feature... but we
+				// don't
+				// want dropped tables to be included here; in fact they may
+				// even result
+				// in errors on reverse engineering as their names have special
+				// chars like
+				// "/", etc. So skip them all together
+
+				String name = rs.getString("TABLE_NAME");
+				if (name == null) {
+					continue;
+				}
+
+				DetectedDbEntity table = new DetectedDbEntity(name);
+
+				String catalog = rs.getString("TABLE_CAT");
+				table.setCatalog(catalog);
+
+				String schema = rs.getString("TABLE_SCHEM");
+				table.setSchema(schema);
+				if (!(this.catalog == null || this.catalog.equals(catalog))
+						|| !(this.schema == null || this.schema.equals(schema))) {
+
+					LOGGER.error(catalog + "." + schema + "." + name + " wrongly loaded for catalog/schema : "
+							+ this.catalog + "." + this.schema);
+
+					continue;
+				}
+
+				PatternFilter includeTable = filters.isIncludeTable(table.getName());
+				if (includeTable != null) {
+					tables.add(table);
+				}
+			}
+		}
+		return tables;
+	}
+
+	/**
+	 * Loads dbEntities for the specified tables.
+	 * 
+	 * @param config
+	 * @param types
+	 */
+	public List<DbEntity> loadDbEntities(DataMap map, DbLoaderConfiguration config, String[] types) throws SQLException {
+		/** List of db entities to process. */
+
+		List<DetectedDbEntity> tables = getDbEntities(config.getFiltersConfig().tableFilter(catalog, schema), types);
+
+		List<DbEntity> dbEntities = new ArrayList<DbEntity>();
+		for (DbEntity dbEntity : tables) {
+			DbEntity oldEnt = map.getDbEntity(dbEntity.getName());
+			if (oldEnt != null) {
+				Collection<ObjEntity> oldObjEnt = map.getMappedEntities(oldEnt);
+				if (!oldObjEnt.isEmpty()) {
+					for (ObjEntity objEntity : oldObjEnt) {
+						LOGGER.debug("Delete ObjEntity: " + objEntity.getName());
+						map.removeObjEntity(objEntity.getName(), true);
+						delegate.objEntityRemoved(objEntity);
+					}
+				}
+
+				LOGGER.debug("Overwrite DbEntity: " + oldEnt.getName());
+				map.removeDbEntity(oldEnt.getName(), true);
+				delegate.dbEntityRemoved(oldEnt);
+			}
+
+			map.addDbEntity(dbEntity);
+
+			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) {
+				dbEntities.add(dbEntity);
+				attributesLoader.loadDbAttributes(dbEntity);
+				if (!config.isSkipPrimaryKeyLoading()) {
+					loadPrimaryKey(dbEntity);
+				}
+			}
+		}
+
+		return dbEntities;
+	}
+
+	private void loadPrimaryKey(DbEntity dbEntity) throws SQLException {
+
+		try (ResultSet rs = metaData.getPrimaryKeys(dbEntity.getCatalog(), dbEntity.getSchema(), dbEntity.getName());) {
+			while (rs.next()) {
+				String columnName = rs.getString("COLUMN_NAME");
+				DbAttribute attribute = dbEntity.getAttribute(columnName);
+
+				if (attribute != null) {
+					attribute.setPrimaryKey(true);
+				} else {
+					// why an attribute might be null is not quiet clear
+					// but there is a bug report 731406 indicating that it is
+					// possible
+					// so just print the warning, and ignore
+					LOGGER.warn("Can't locate attribute for primary key: " + columnName);
+				}
+
+				String pkName = rs.getString("PK_NAME");
+				if (pkName != null && dbEntity instanceof DetectedDbEntity) {
+					((DetectedDbEntity) dbEntity).setPrimaryKeyName(pkName);
+				}
+
+			}
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DefaultDbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DefaultDbLoaderDelegate.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DefaultDbLoaderDelegate.java
new file mode 100644
index 0000000..b39fd5f
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DefaultDbLoaderDelegate.java
@@ -0,0 +1,59 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * @since 4.0.
+ */
+public class DefaultDbLoaderDelegate implements DbLoaderDelegate {
+
+    @Override
+    public void dbEntityAdded(DbEntity entity) {
+
+    }
+
+    @Override
+    public void dbEntityRemoved(DbEntity entity) {
+
+    }
+
+    @Override
+    public boolean dbRelationship(DbEntity entity) {
+        return true;
+    }
+
+    @Override
+    public boolean dbRelationshipLoaded(DbEntity entity, DbRelationship relationship) {
+        return true;
+    }
+
+    @Override
+    public void objEntityAdded(ObjEntity entity) {
+
+    }
+
+    @Override
+    public void objEntityRemoved(ObjEntity entity) {
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/FiltersConfigBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/FiltersConfigBuilder.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/FiltersConfigBuilder.java
new file mode 100644
index 0000000..390d91a
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/FiltersConfigBuilder.java
@@ -0,0 +1,393 @@
+/*****************************************************************
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.dbsync.reverse;
+
+import org.apache.cayenne.dbimport.Catalog;
+import org.apache.cayenne.dbimport.ExcludeColumn;
+import org.apache.cayenne.dbimport.ExcludeProcedure;
+import org.apache.cayenne.dbimport.ExcludeTable;
+import org.apache.cayenne.dbimport.IncludeColumn;
+import org.apache.cayenne.dbimport.IncludeProcedure;
+import org.apache.cayenne.dbimport.IncludeTable;
+import org.apache.cayenne.dbimport.PatternParam;
+import org.apache.cayenne.dbimport.ReverseEngineering;
+import org.apache.cayenne.dbimport.Schema;
+import org.apache.cayenne.dbsync.reverse.filters.LegacyFilterConfigBridge;
+import org.apache.cayenne.dbsync.reverse.filters.CatalogFilter;
+import org.apache.cayenne.dbsync.reverse.filters.IncludeTableFilter;
+import org.apache.cayenne.dbsync.reverse.filters.SchemaFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+/**
+ * @since 4.0.
+ */
+public final class FiltersConfigBuilder {
+
+    private final ReverseEngineering engineering;
+
+    public FiltersConfigBuilder(ReverseEngineering engineering) {
+        this.engineering = engineering;
+    }
+
+    public FiltersConfig filtersConfig() {
+        compact();
+
+        return new FiltersConfig(transformCatalogs(engineering.getCatalogs()));
+    }
+
+    private CatalogFilter[] transformCatalogs(Collection<Catalog> catalogs) {
+        CatalogFilter[] catalogFilters = new CatalogFilter[catalogs.size()];
+        int i = 0;
+        for (Catalog catalog : catalogs) {
+            catalogFilters[i] = new CatalogFilter(catalog.getName(), transformSchemas(catalog.getSchemas()));
+            i++;
+        }
+
+        return catalogFilters;
+    }
+
+    private SchemaFilter[] transformSchemas(Collection<Schema> schemas) {
+        SchemaFilter[] schemaFilters = new SchemaFilter[schemas.size()];
+        int i = 0;
+        for (Schema schema : schemas) {
+            schemaFilters[i] = new SchemaFilter(schema.getName(),
+                    new TableFilter(transformIncludeTable(schema.getIncludeTables()),
+                            transformExcludeTable(schema.getExcludeTables())),
+                    transform(schema.getIncludeProcedures(), schema.getExcludeProcedures()));
+            i++;
+        }
+
+        return schemaFilters;
+    }
+
+    private SortedSet<Pattern> transformExcludeTable(Collection<ExcludeTable> excludeTables) {
+        SortedSet<Pattern> res = new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR);
+        for (ExcludeTable exclude : excludeTables) {
+            res.add(PatternFilter.pattern(exclude.getPattern()));
+        }
+        return res;
+    }
+
+    private SortedSet<IncludeTableFilter> transformIncludeTable(Collection<IncludeTable> includeTables) {
+        SortedSet<IncludeTableFilter> includeTableFilters = new TreeSet<IncludeTableFilter>();
+        for (IncludeTable includeTable : includeTables) {
+            includeTableFilters.add(new IncludeTableFilter(includeTable.getPattern(),
+                    transform(includeTable.getIncludeColumns(), includeTable.getExcludeColumns())));
+        }
+
+        return includeTableFilters;
+    }
+
+    private PatternFilter transform(Collection<? extends PatternParam> include,
+                                    Collection<? extends PatternParam> exclude) {
+        PatternFilter filter = new PatternFilter();
+
+        for (PatternParam patternParam : include) {
+            filter.include(patternParam.getPattern());
+        }
+
+        for (PatternParam patternParam : exclude) {
+            filter.exclude(patternParam.getPattern());
+        }
+
+        return filter;
+
+    }
+
+    /**
+     * Goal of this method transform ReverseEngineering config into more regular form
+     * From
+     *      ReverseEngineering
+     *          Catalog
+     *              Schema
+     *                  IncludeTable
+     *                      IncludeColumn
+     *                      ExcludeColumn
+     *                  ExcludeTable
+     *                  IncludeProcedures
+     *                  ExcludeProcedures
+     *                  IncludeColumn
+     *                  ExcludeColumn
+     *              IncludeTable
+     *                  IncludeColumn
+     *                  ExcludeColumn
+     *              ExcludeTable
+     *              IncludeProcedures
+     *              ExcludeProcedures
+     *              IncludeColumn
+     *              ExcludeColumn
+     *          Schema
+     *              IncludeTable
+     *                  IncludeColumn
+     *                  ExcludeColumn
+     *              ExcludeTable
+     *              IncludeProcedures
+     *              ExcludeProcedures
+     *              IncludeColumn
+     *              ExcludeColumn
+     *          IncludeTable
+     *              IncludeColumn
+     *              ExcludeColumn
+     *          ExcludeTable
+     *          IncludeProcedures
+     *          ExcludeProcedures
+     *          IncludeColumn
+     *          ExcludeColumn
+     *
+     * Into
+     *      ReverseEngineering
+     *          Catalog
+     *              Schema
+     *                  IncludeTable
+     *                      IncludeColumn
+     *                      ExcludeColumn
+     *                  ExcludeTable
+     *                  IncludeProcedures
+     *                  ExcludeProcedures
+     *
+     *
+     * */
+    public void compact() {
+        addEmptyElements();
+
+        compactColumnFilters();
+        compactTableFilter();
+        compactProcedureFilter();
+        compactSchemas();
+    }
+
+    private void compactSchemas() {
+        for (Catalog catalog : engineering.getCatalogs()) {
+            catalog.getSchemas().addAll(engineering.getSchemas());
+        }
+        engineering.setSchemas(null);
+    }
+
+    private void compactProcedureFilter() {
+        Collection<IncludeProcedure> engIncludeProcedures = engineering.getIncludeProcedures();
+        Collection<ExcludeProcedure> engExcludeProcedures = engineering.getExcludeProcedures();
+
+        engineering.setIncludeProcedures(null);
+        engineering.setExcludeProcedures(null);
+
+        for (Catalog catalog : engineering.getCatalogs()) {
+            Collection<IncludeProcedure> catalogIncludeProcedures = catalog.getIncludeProcedures();
+            Collection<ExcludeProcedure> catalogExcludeProcedures = catalog.getExcludeProcedures();
+
+            catalog.setIncludeProcedures(null);
+            catalog.setExcludeProcedures(null);
+
+            for (Schema schema : catalog.getSchemas()) {
+                if (engIncludeProcedures != null) {
+                    schema.getIncludeProcedures().addAll(engIncludeProcedures);
+                    schema.getIncludeProcedures().addAll(catalogIncludeProcedures);
+                }
+                if (engExcludeProcedures != null) {
+                    schema.getExcludeProcedures().addAll(engExcludeProcedures);
+                    schema.getExcludeProcedures().addAll(catalogExcludeProcedures);
+                }
+            }
+        }
+
+        for (Schema schema : engineering.getSchemas()) {
+            schema.getIncludeProcedures().addAll(engIncludeProcedures);
+            schema.getExcludeProcedures().addAll(engExcludeProcedures);
+        }
+    }
+
+    private void compactTableFilter() {
+        Collection<IncludeTable> engIncludeTables = engineering.getIncludeTables();
+        Collection<ExcludeTable> engExcludeTables = engineering.getExcludeTables();
+
+        engineering.setIncludeTables(null);
+        engineering.setExcludeTables(null);
+
+        for (Catalog catalog : engineering.getCatalogs()) {
+            Collection<IncludeTable> catalogIncludeTables = catalog.getIncludeTables();
+            Collection<ExcludeTable> catalogExcludeTables = catalog.getExcludeTables();
+
+            catalog.setIncludeTables(null);
+            catalog.setExcludeTables(null);
+
+            for (Schema schema : catalog.getSchemas()) {
+                if (engIncludeTables != null) {
+                    schema.getIncludeTables().addAll(engIncludeTables);
+                    schema.getIncludeTables().addAll(catalogIncludeTables);
+                }
+                if (engExcludeTables != null) {
+                    schema.getExcludeTables().addAll(engExcludeTables);
+                    schema.getExcludeTables().addAll(catalogExcludeTables);
+                }
+            }
+        }
+
+        for (Schema schema : engineering.getSchemas()) {
+            schema.getIncludeTables().addAll(engIncludeTables);
+            schema.getExcludeTables().addAll(engExcludeTables);
+        }
+    }
+
+    private void compactColumnFilters() {
+        Collection<IncludeColumn> engIncludeColumns = engineering.getIncludeColumns();
+        Collection<ExcludeColumn> engExcludeColumns = engineering.getExcludeColumns();
+
+        engineering.setIncludeColumns(null);
+        engineering.setExcludeColumns(null);
+
+        for (Catalog catalog : engineering.getCatalogs()) {
+            Collection<IncludeColumn> catalogIncludeColumns = catalog.getIncludeColumns();
+            Collection<ExcludeColumn> catalogExcludeColumns = catalog.getExcludeColumns();
+
+            catalog.setIncludeColumns(null);
+            catalog.setExcludeColumns(null);
+
+            for (Schema schema : catalog.getSchemas()) {
+                Collection<IncludeColumn> schemaIncludeColumns = schema.getIncludeColumns();
+                Collection<ExcludeColumn> schemaExcludeColumns = schema.getExcludeColumns();
+
+                schema.setIncludeColumns(null);
+                schema.setExcludeColumns(null);
+
+                if (schema != null) {
+                    for (IncludeTable includeTable : schema.getIncludeTables()) {
+                        if (engIncludeColumns != null) {
+                            includeTable.getIncludeColumns().addAll(engIncludeColumns);
+                            includeTable.getIncludeColumns().addAll(catalogIncludeColumns);
+                            includeTable.getIncludeColumns().addAll(schemaIncludeColumns);
+                        }
+                        if (engExcludeColumns != null) {
+                            includeTable.getExcludeColumns().addAll(engExcludeColumns);
+                            includeTable.getExcludeColumns().addAll(catalogExcludeColumns);
+                            includeTable.getExcludeColumns().addAll(schemaExcludeColumns);
+                        }
+                    }
+                }
+            }
+
+            if (catalog.getIncludeTables() != null) {
+                for (IncludeTable includeTable : catalog.getIncludeTables()) {
+                    includeTable.getIncludeColumns().addAll(engIncludeColumns);
+                    includeTable.getIncludeColumns().addAll(catalogIncludeColumns);
+
+                    includeTable.getExcludeColumns().addAll(engExcludeColumns);
+                    includeTable.getExcludeColumns().addAll(catalogExcludeColumns);
+                }
+            }
+        }
+
+        for (Schema schema : engineering.getSchemas()) {
+            Collection<IncludeColumn> schemaIncludeColumns = schema.getIncludeColumns();
+            Collection<ExcludeColumn> schemaExcludeColumns = schema.getExcludeColumns();
+
+            schema.setIncludeColumns(null);
+            schema.setExcludeColumns(null);
+
+            for (IncludeTable includeTable : schema.getIncludeTables()) {
+                includeTable.getIncludeColumns().addAll(engIncludeColumns);
+                includeTable.getIncludeColumns().addAll(schemaIncludeColumns);
+
+                includeTable.getExcludeColumns().addAll(engExcludeColumns);
+                includeTable.getExcludeColumns().addAll(schemaExcludeColumns);
+            }
+        }
+
+        if (engineering.getIncludeTables() != null) {
+            for (IncludeTable includeTable : engineering.getIncludeTables()) {
+                includeTable.getIncludeColumns().addAll(engIncludeColumns);
+                includeTable.getExcludeColumns().addAll(engExcludeColumns);
+            }
+        }
+    }
+
+    private void addEmptyElements() {
+        if (engineering.getCatalogs().isEmpty()) {
+            engineering.addCatalog(new Catalog());
+        }
+
+        for (Catalog catalog : engineering.getCatalogs()) {
+            if (catalog.getSchemas().isEmpty()
+                    && engineering.getSchemas().isEmpty()) {
+                catalog.addSchema(new Schema());
+            }
+
+            for (Schema schema : catalog.getSchemas()) {
+                if (schema.getIncludeTables().isEmpty()
+                        && catalog.getIncludeTables().isEmpty()
+                        && engineering.getIncludeTables().isEmpty()) {
+
+                    schema.addIncludeTable(new IncludeTable());
+                }
+            }
+        }
+
+        if (engineering.getSchemas() == null) {
+            engineering.setSchemas(new LinkedList<Schema>());
+        }
+
+        for (Schema schema : engineering.getSchemas()) {
+            if (schema.getIncludeTables().isEmpty()
+                    && engineering.getIncludeTables().isEmpty()) {
+
+                schema.addIncludeTable(new IncludeTable());
+            }
+        }
+    }
+
+    public FiltersConfigBuilder add(LegacyFilterConfigBridge build) {
+        if (!isBlank(build.catalog())) {
+            engineering.addCatalog(new Catalog(build.catalog()));
+        }
+
+        if (!isBlank(build.schema())) {
+            engineering.addSchema(new Schema(build.schema()));
+        }
+
+        if (!isBlank(build.getIncludeTableFilters())) {
+            engineering.addIncludeTable(new IncludeTable(build.getIncludeTableFilters()));
+        }
+        if (!isBlank(build.getExcludeTableFilters())) {
+            engineering.addExcludeTable(new ExcludeTable(build.getExcludeTableFilters()));
+        }
+
+        if (!isBlank(build.getIncludeColumnFilters())) {
+            engineering.addIncludeColumn(new IncludeColumn(build.getIncludeColumnFilters()));
+        }
+        if (!isBlank(build.getExcludeColumnFilters())) {
+            engineering.addExcludeColumn(new ExcludeColumn(build.getExcludeColumnFilters()));
+        }
+
+        if (build.isLoadProcedures()) {
+            if (!isBlank(build.getIncludeProceduresFilters())) {
+                engineering.addIncludeProcedure(new IncludeProcedure(build.getIncludeProceduresFilters()));
+            }
+            if (!isBlank(build.getExcludeProceduresFilters())) {
+                engineering.addExcludeProcedure(new ExcludeProcedure(build.getExcludeProceduresFilters()));
+            }
+        }
+
+        return this;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/LoggingDbLoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/LoggingDbLoaderDelegate.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/LoggingDbLoaderDelegate.java
new file mode 100644
index 0000000..3777ccc
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/LoggingDbLoaderDelegate.java
@@ -0,0 +1,76 @@
+/*
+ * 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.dbsync.reverse;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.commons.logging.Log;
+
+/**
+ * @since 4.0
+ */
+public class LoggingDbLoaderDelegate extends DefaultDbLoaderDelegate {
+
+    private final Log logger;
+
+    public LoggingDbLoaderDelegate(Log logger) {
+        this.logger = logger;
+    }
+
+    @Override
+    public void dbEntityAdded(DbEntity entity) {
+        logger.info("  Table: " + entity.getFullyQualifiedName());
+    }
+
+    @Override
+    public void dbEntityRemoved(DbEntity entity) {
+        logger.info("  Table removed: " + entity.getFullyQualifiedName());
+    }
+
+    @Override
+    public boolean dbRelationship(DbEntity entity) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("    Relationships for " + entity.getFullyQualifiedName());
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean dbRelationshipLoaded(DbEntity entity, DbRelationship relationship) {
+        logger.info("    " + relationship);
+
+        return true;
+    }
+
+    @Override
+    public void objEntityAdded(ObjEntity entity) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("  Class: " + entity.getName());
+        }
+    }
+
+    @Override
+    public void objEntityRemoved(ObjEntity entity) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("  Class removed: " + entity.getName());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/ManyToManyCandidateEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/ManyToManyCandidateEntity.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/ManyToManyCandidateEntity.java
new file mode 100644
index 0000000..9666e08
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/ManyToManyCandidateEntity.java
@@ -0,0 +1,142 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.naming.DefaultUniqueNameGenerator;
+import org.apache.cayenne.map.naming.ExportedKey;
+import org.apache.cayenne.map.naming.NameCheckers;
+import org.apache.cayenne.map.naming.ObjectNameGenerator;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class represent ObjEntity that may be optimized using flattened relationships
+ * as many to many table
+ */
+public class ManyToManyCandidateEntity {
+
+    private static final Log LOG = LogFactory.getLog(ManyToManyCandidateEntity.class);
+
+    private final ObjEntity joinEntity;
+
+    private final DbRelationship dbRel1;
+    private final DbRelationship dbRel2;
+
+    private final ObjEntity entity1;
+    private final ObjEntity entity2;
+
+    private final DbRelationship reverseRelationship1;
+    private final DbRelationship reverseRelationship2;
+
+    private ManyToManyCandidateEntity(ObjEntity entityValue, List<ObjRelationship> relationships) {
+        joinEntity = entityValue;
+
+        ObjRelationship rel1 = relationships.get(0);
+        ObjRelationship rel2 = relationships.get(1);
+
+        dbRel1 = rel1.getDbRelationships().get(0);
+        dbRel2 = rel2.getDbRelationships().get(0);
+
+        reverseRelationship1 = dbRel1.getReverseRelationship();
+        reverseRelationship2 = dbRel2.getReverseRelationship();
+
+        entity1 = rel1.getTargetEntity();
+        entity2 = rel2.getTargetEntity();
+    }
+
+    /**
+     * Method check - if current entity represent many to many temporary table
+     * @return true if current entity is represent many to many table; otherwise returns false
+     */
+    public static ManyToManyCandidateEntity build(ObjEntity joinEntity) {
+        ArrayList<ObjRelationship> relationships = new ArrayList<ObjRelationship>(joinEntity.getRelationships());
+        if (relationships.size() != 2 || (relationships.get(0).getDbRelationships().isEmpty() || relationships.get(1).getDbRelationships().isEmpty())) {
+            return null;
+        }
+
+        ManyToManyCandidateEntity candidateEntity = new ManyToManyCandidateEntity(joinEntity, relationships);
+        if (candidateEntity.isManyToMany()) {
+            return candidateEntity;
+        }
+
+        return null;
+    }
+
+    private boolean isManyToMany() {
+        boolean isNotHaveAttributes = joinEntity.getAttributes().size() == 0;
+
+        return isNotHaveAttributes
+                && reverseRelationship1 != null && reverseRelationship1.isToDependentPK()
+                && reverseRelationship2 != null && reverseRelationship2.isToDependentPK()
+                && entity1 != null && entity2 != null;
+    }
+
+    private void addFlattenedRelationship(ObjectNameGenerator nameGenerator, ObjEntity srcEntity, ObjEntity dstEntity,
+                                          DbRelationship rel1, DbRelationship rel2) {
+
+        if (rel1.getSourceAttributes().isEmpty() && rel2.getTargetAttributes().isEmpty()) {
+            LOG.warn("Wrong call ManyToManyCandidateEntity.addFlattenedRelationship(... , " + srcEntity.getName()
+                    + ", " + dstEntity.getName() + ", ...)");
+
+            return;
+        }
+
+        ExportedKey key = new ExportedKey(
+                rel1.getSourceEntity().getName(),
+                rel1.getSourceAttributes().iterator().next().getName(),
+                null,
+                rel2.getTargetEntity().getName(),
+                rel2.getTargetAttributes().iterator().next().getName(),
+                null,
+                (short) 1);
+
+        ObjRelationship newRelationship = new ObjRelationship();
+        newRelationship.setName(DefaultUniqueNameGenerator.generate(NameCheckers.objRelationship, srcEntity,
+                nameGenerator.createDbRelationshipName(key, true)));
+
+        newRelationship.setSourceEntity(srcEntity);
+        newRelationship.setTargetEntityName(dstEntity);
+
+        newRelationship.addDbRelationship(rel1);
+        newRelationship.addDbRelationship(rel2);
+
+        srcEntity.addRelationship(newRelationship);
+    }
+
+    /**
+     * Method make direct relationships between 2 entities and remove relationships to
+     * many to many entity
+     *
+     * @param nameGenerator
+     */
+    public void optimizeRelationships(ObjectNameGenerator nameGenerator) {
+        entity1.removeRelationship(reverseRelationship1.getName());
+        entity2.removeRelationship(reverseRelationship2.getName());
+
+        addFlattenedRelationship(nameGenerator, entity1, entity2, reverseRelationship1, dbRel2);
+        addFlattenedRelationship(nameGenerator, entity2, entity1, reverseRelationship2, dbRel1);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/NameFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/NameFilter.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/NameFilter.java
new file mode 100644
index 0000000..89b8330
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/NameFilter.java
@@ -0,0 +1,27 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+/**
+ * @since 4.0.
+ */
+public interface NameFilter {
+
+    boolean isIncluded(String string);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/NamePatternMatcher.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/NamePatternMatcher.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/NamePatternMatcher.java
new file mode 100644
index 0000000..cb9faad
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/NamePatternMatcher.java
@@ -0,0 +1,225 @@
+/*****************************************************************
+ *   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.dbsync.reverse;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.apache.cayenne.util.CayenneMapEntry;
+import org.apache.commons.logging.Log;
+
+/**
+ * Provides name pattern matching functionality.
+ * 
+ * @since 1.2
+ */
+public class NamePatternMatcher implements NameFilter {
+
+    private static final String[] EMPTY_ARRAY = new String[0];
+    private static final Pattern COMMA = Pattern.compile(",");
+
+    private final Pattern[] itemIncludeFilters;
+    private final Pattern[] itemExcludeFilters;
+
+    public static NamePatternMatcher build(Log logger, String includePattern, String excludePattern) {
+        return new NamePatternMatcher(createPatterns(logger, includePattern), createPatterns(logger, excludePattern));
+    }
+
+    public NamePatternMatcher(Pattern[] itemIncludeFilters, Pattern[] itemExcludeFilters) {
+        this.itemIncludeFilters = itemIncludeFilters;
+        this.itemExcludeFilters = itemExcludeFilters;
+    }
+
+    /**
+     * Applies preconfigured list of filters to the list, removing entities that do not
+     * pass the filter.
+     * 
+     * @deprecated since 3.0 still used by AntDataPortDelegate, which itself should
+     *             probably be deprecated
+     */
+    @Deprecated
+    public List<?> filter(List<?> items) {
+        if (items == null || items.isEmpty()) {
+            return items;
+        }
+
+        if (itemIncludeFilters.length == 0 && itemExcludeFilters.length == 0) {
+            return items;
+        }
+
+        Iterator<?> it = items.iterator();
+        while (it.hasNext()) {
+            CayenneMapEntry entity = (CayenneMapEntry) it.next();
+
+            if (!passedIncludeFilter(entity.getName())) {
+                it.remove();
+                continue;
+            }
+
+            if (!passedExcludeFilter(entity.getName())) {
+                it.remove();
+            }
+        }
+
+        return items;
+    }
+
+    /**
+     * Returns an array of Patterns. Takes a comma-separated list of patterns, attempting
+     * to convert them to the java.util.regex.Pattern syntax. E.g.
+     * <p>
+     * <code>"billing_*,user?"</code> will become an array of two expressions:
+     * <p>
+     * <code>^billing_.*$</code><br>
+     * <code>^user.?$</code><br>
+     */
+    public static Pattern[] createPatterns(Log logger, String patternString) {
+        if (patternString == null) {
+            return new Pattern[0];
+        }
+        String[] patternStrings = tokenizePattern(patternString);
+        List<Pattern> patterns = new ArrayList<Pattern>(patternStrings.length);
+
+        for (String patternString1 : patternStrings) {
+
+            // test the pattern
+            try {
+                patterns.add(Pattern.compile(patternString1));
+            } catch (PatternSyntaxException e) {
+
+                if (logger != null) {
+                    logger.warn("Ignoring invalid pattern [" + patternString1 + "], reason: " + e.getMessage());
+                }
+            }
+        }
+
+        return patterns.toArray(new Pattern[patterns.size()]);
+    }
+
+    /**
+     * Returns an array of valid regular expressions. Takes a comma-separated list of
+     * patterns, attempting to convert them to the java.util.regex.Pattern syntax. E.g.
+     * <p>
+     * <code>"billing_*,user?"</code> will become an array of two expressions:
+     * <p>
+     * <code>^billing_.*$</code><br>
+     * <code>^user.?$</code><br>
+     */
+    public static String[] tokenizePattern(String pattern) {
+        if (pattern == null || pattern.isEmpty()) {
+            return EMPTY_ARRAY;
+        }
+
+        String[] patterns = COMMA.split(pattern);
+        if (patterns.length == 0) {
+            return EMPTY_ARRAY;
+        }
+
+        for (int i = 0; i < patterns.length; i++) {
+            // convert * into regex syntax
+            // e.g. abc*x becomes ^abc.*x$
+            // or abc?x becomes ^abc.?x$
+            patterns[i] = "^" + patterns[i].replaceAll("[*?]", ".$0") + "$";
+        }
+
+        return patterns;
+    }
+
+    /**
+     * Returns true if a given object property satisfies the include/exclude patterns.
+     * 
+     * @since 3.0
+     */
+    @Override
+    public boolean isIncluded(String string) {
+        return passedIncludeFilter(string) && passedExcludeFilter(string);
+    }
+
+    /**
+     * Returns true if an object matches any one of the "include" patterns, or if there is
+     * no "include" patterns defined.
+     * 
+     * @since 3.0
+     */
+    private boolean passedIncludeFilter(String item) {
+        if (itemIncludeFilters.length == 0) {
+            return true;
+        }
+
+        for (Pattern itemIncludeFilter : itemIncludeFilters) {
+            if (itemIncludeFilter.matcher(item).find()) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if an object does not match any one of the "exclude" patterns, or if
+     * there is no "exclude" patterns defined.
+     * 
+     * @since 3.0
+     */
+    private boolean passedExcludeFilter(String item) {
+        if (itemExcludeFilters.length == 0) {
+            return true;
+        }
+
+        for (Pattern itemExcludeFilter : itemExcludeFilters) {
+            if (itemExcludeFilter.matcher(item).find()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static String replaceWildcardInStringWithString(
+            String wildcard,
+            String pattern,
+            String replacement) {
+
+        if (pattern == null || wildcard == null) {
+            return pattern;
+        }
+
+        StringBuilder buffer = new StringBuilder();
+        int lastPos = 0;
+        int wildCardPos = pattern.indexOf(wildcard);
+        while (wildCardPos != -1) {
+            if (lastPos != wildCardPos) {
+                buffer.append(pattern.substring(lastPos, wildCardPos));
+            }
+            buffer.append(replacement);
+            lastPos += wildCardPos + wildcard.length();
+            wildCardPos = pattern.indexOf(wildcard, lastPos);
+        }
+
+        if (lastPos < pattern.length()) {
+            buffer.append(pattern.substring(lastPos));
+        }
+
+        return buffer.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/CatalogFilter.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/CatalogFilter.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/CatalogFilter.java
new file mode 100644
index 0000000..46feb9d
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/CatalogFilter.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.dbsync.reverse.filters;
+
+import java.util.Arrays;
+
+/**
+* @since 4.0.
+*/
+public class CatalogFilter {
+    public final String name;
+    public final SchemaFilter[] schemas;
+
+    public CatalogFilter(String name, SchemaFilter... schemas) {
+        if (schemas == null || schemas.length == 0) {
+            throw new IllegalArgumentException("schemas(" + Arrays.toString(schemas) + ") can't be null or empty");
+        }
+
+        this.name = name;
+        this.schemas = schemas;
+    }
+
+    public SchemaFilter getSchema(String schema) {
+        for (SchemaFilter schemaFilter : schemas) {
+            if (schemaFilter.name == null || schemaFilter.name.equals(schema)) {
+                return schemaFilter;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return toString(new StringBuilder(), "").toString();
+    }
+
+    public StringBuilder toString(StringBuilder res, String prefix) {
+        res.append(prefix).append("Catalog: ").append(name).append("\n");
+        for (SchemaFilter schema : schemas) {
+            schema.toString(res, prefix + "  ");
+        }
+
+        return res;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfig.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfig.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfig.java
new file mode 100644
index 0000000..48a2e62
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/filters/FiltersConfig.java
@@ -0,0 +1,81 @@
+/*****************************************************************
+ *   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.dbsync.reverse.filters;
+
+import java.util.Arrays;
+
+/**
+ * @since 4.0.
+ */
+public class FiltersConfig {
+
+    public final CatalogFilter[] catalogs;
+
+    public FiltersConfig(CatalogFilter ... catalogs) {
+        if (catalogs == null || catalogs.length == 0) {
+            throw new IllegalArgumentException("catalogs(" + Arrays.toString(catalogs) + ") can't be null or empty");
+        }
+
+        this.catalogs = catalogs;
+    }
+
+    public PatternFilter proceduresFilter(String catalog, String schema) {
+        return getSchemaFilter(catalog, schema).procedures;
+    }
+
+    public TableFilter tableFilter(String catalog, String schema) {
+        return getSchemaFilter(catalog, schema).tables;
+    }
+
+    protected SchemaFilter getSchemaFilter(String catalog, String schema) {
+        CatalogFilter catalogFilter = getCatalog(catalog);
+        if (catalogFilter == null) {
+            return null;
+        }
+
+        return catalogFilter.getSchema(schema);
+    }
+
+    protected CatalogFilter getCatalog(String catalog) {
+        for (CatalogFilter catalogFilter : catalogs) {
+            if (catalogFilter.name == null || catalogFilter.name.equals(catalog)) {
+                return catalogFilter;
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        for (CatalogFilter catalog : catalogs) {
+            catalog.toString(builder, "");
+        }
+
+        return builder.toString();
+    }
+
+    public static FiltersConfig create(String catalog, String schema, TableFilter tableFilter, PatternFilter procedures) {
+        return new FiltersConfig(
+                    new CatalogFilter(catalog,
+                        new SchemaFilter(schema, tableFilter, procedures)));
+    }
+}


[04/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/SetPrimaryKeyToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetPrimaryKeyToModel.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetPrimaryKeyToModel.java
deleted file mode 100644
index 10ec4fe..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetPrimaryKeyToModel.java
+++ /dev/null
@@ -1,77 +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.merge;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.event.AttributeEvent;
-
-public class SetPrimaryKeyToModel extends AbstractToModelToken.Entity {
-
-    private Collection<DbAttribute> primaryKeyOriginal;
-    private Collection<DbAttribute> primaryKeyNew;
-    private String detectedPrimaryKeyName;
-    private Set<String> primaryKeyNewAttributeNames = new HashSet<String>();
-
-    public SetPrimaryKeyToModel(DbEntity entity,
-            Collection<DbAttribute> primaryKeyOriginal,
-            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
-        super("Set Primary Key", entity);
-        
-        this.primaryKeyOriginal = primaryKeyOriginal;
-        this.primaryKeyNew = primaryKeyNew;
-        this.detectedPrimaryKeyName = detectedPrimaryKeyName;
-        
-        for (DbAttribute attr : primaryKeyNew) {
-            primaryKeyNewAttributeNames.add(attr.getName().toUpperCase());
-        }
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return factory.createSetPrimaryKeyToDb(
-                getEntity(),
-                primaryKeyNew,
-                primaryKeyOriginal,
-                detectedPrimaryKeyName);
-    }
-
-    public void execute(MergerContext mergerContext) {
-        DbEntity e = getEntity();
-
-        for (DbAttribute attr : e.getAttributes()) {
-
-            boolean wasPrimaryKey = attr.isPrimaryKey();
-            boolean willBePrimaryKey = primaryKeyNewAttributeNames.contains(attr
-                    .getName()
-                    .toUpperCase());
-
-            if (wasPrimaryKey != willBePrimaryKey) {
-                attr.setPrimaryKey(willBePrimaryKey);
-                e.dbAttributeChanged(new AttributeEvent(this, attr, e));
-                mergerContext.getModelMergeDelegate().dbAttributeModified(attr);
-            }
-
-        }
-
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/SetValueForNullToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetValueForNullToDb.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/SetValueForNullToDb.java
deleted file mode 100644
index 1991154..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/SetValueForNullToDb.java
+++ /dev/null
@@ -1,46 +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.merge;
-
-import java.util.List;
-
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-
-public class SetValueForNullToDb extends AbstractToDbToken.EntityAndColumn {
-    
-    private ValueForNullProvider valueForNullProvider;
-
-    public SetValueForNullToDb(DbEntity entity, DbAttribute column, ValueForNullProvider valueForNullProvider) {
-        super("Set value for null", entity, column);
-        this.valueForNullProvider = valueForNullProvider;
-    }
-    
-    @Override
-    public List<String> createSql(DbAdapter adapter) {
-        return valueForNullProvider.createSql(getEntity(), getColumn());
-    }
-
-    public MergerToken createReverse(MergerFactory factory) {
-        return new DummyReverseToken(this);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/main/java/org/apache/cayenne/merge/ValueForNullProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/merge/ValueForNullProvider.java b/cayenne-server/src/main/java/org/apache/cayenne/merge/ValueForNullProvider.java
deleted file mode 100644
index ff147eb..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/merge/ValueForNullProvider.java
+++ /dev/null
@@ -1,42 +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.merge;
-
-import java.util.List;
-
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-
-/**
- * Class that will be used to set value for null on not
- * null columns
- */
-public interface ValueForNullProvider {
-    
-    /**
-     * @return true if there exist a value that should be inserted for null values
-     */
-    public boolean hasValueFor(DbEntity entity, DbAttribute column);
-
-    /**
-     * @return a {@link List} of sql to set value for null
-     */
-    public List<String> createSql(DbEntity entity, DbAttribute column);
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index 7c12600..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/util/EntityMergeSupport.java
+++ /dev/null
@@ -1,520 +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.util;
-
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.Entity;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
-import org.apache.cayenne.map.naming.DefaultUniqueNameGenerator;
-import org.apache.cayenne.map.naming.LegacyNameGenerator;
-import org.apache.cayenne.map.naming.NameCheckers;
-import org.apache.cayenne.map.naming.ObjectNameGenerator;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Implements methods for entity merging.
- */
-public class EntityMergeSupport {
-
-    private static final Log LOG = LogFactory.getLog(EntityMergeSupport.class);
-
-    private static final Map<String, String> CLASS_TO_PRIMITIVE;
-
-    static {
-        CLASS_TO_PRIMITIVE = new HashMap<>();
-        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");
-    }
-
-    private final DataMap map;
-    /**
-     * Strategy for choosing names for entities, attributes and relationships
-     */
-    private final ObjectNameGenerator nameGenerator;
-    /**
-     * Listeners of merge process.
-     */
-    private final List<EntityMergeListener> listeners = new ArrayList<EntityMergeListener>();
-    protected boolean removeMeaningfulFKs;
-    protected boolean removeMeaningfulPKs;
-    protected boolean usePrimitives;
-
-    public EntityMergeSupport(DataMap map) {
-        this(map, new LegacyNameGenerator(), true);
-    }
-
-    /**
-     * @since 3.0
-     */
-    public EntityMergeSupport(DataMap map, ObjectNameGenerator nameGenerator, boolean removeMeaningfulPKs) {
-        this.map = map;
-        this.nameGenerator = nameGenerator;
-        this.removeMeaningfulFKs = true;
-        this.removeMeaningfulPKs = removeMeaningfulPKs;
-
-        /**
-         * Adding a listener, so that all created ObjRelationships would have
-         * default delete rule
-         */
-        addEntityMergeListener(DeleteRuleUpdater.getEntityMergeListener());
-    }
-
-    /**
-     * Updates each one of the collection of ObjEntities, adding attributes and
-     * relationships based on the current state of its DbEntity.
-     *
-     * @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(Iterable<ObjEntity> objEntities) {
-        boolean changed = false;
-        for (ObjEntity nextEntity : objEntities) {
-            if (synchronizeWithDbEntity(nextEntity)) {
-                changed = true;
-            }
-        }
-
-        return changed;
-    }
-
-    /**
-     * @since 4.0
-     */
-    protected boolean removePK(DbEntity dbEntity) {
-        return removeMeaningfulPKs;
-    }
-
-    /**
-     * @since 4.0
-     */
-    protected boolean removeFK(DbEntity dbEntity) {
-        return removeMeaningfulFKs;
-    }
-
-    /**
-     * Updates ObjEntity attributes and relationships based on the current state
-     * of its DbEntity.
-     *
-     * @return true if the ObjEntity has changed as a result of synchronization.
-     */
-    public boolean synchronizeWithDbEntity(ObjEntity entity) {
-
-        if (entity == null) {
-            return false;
-        }
-
-        DbEntity dbEntity = entity.getDbEntity();
-        if (dbEntity == null) {
-            return false;
-        }
-
-        boolean changed = false;
-
-        // synchronization on DataMap is some (weak) protection
-        // against simultaneous modification of the map (like double-clicking on sync button)
-        synchronized (map) {
-
-            if (removeFK(dbEntity)) {
-                changed = getRidOfAttributesThatAreNowSrcAttributesForRelationships(entity);
-            }
-
-            changed |= addMissingAttributes(entity);
-            changed |= addMissingRelationships(entity);
-        }
-
-        return changed;
-    }
-
-    /**
-     * @since 4.0
-     */
-    public boolean synchronizeOnDbAttributeAdded(ObjEntity entity, DbAttribute dbAttribute) {
-
-        Collection<DbRelationship> incomingRels = getIncomingRelationships(dbAttribute.getEntity());
-        if (isMissingFromObjEntity(entity, dbAttribute, incomingRels)) {
-            addMissingAttribute(entity, dbAttribute);
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * @since 4.0
-     */
-    public boolean synchronizeOnDbRelationshipAdded(ObjEntity entity, DbRelationship dbRelationship) {
-
-        if (isMissingFromObjEntity(entity, dbRelationship)) {
-            addMissingRelationship(entity, dbRelationship);
-        }
-
-        return true;
-    }
-
-    private boolean addMissingRelationships(ObjEntity entity) {
-        List<DbRelationship> relationshipsToAdd = getRelationshipsToAdd(entity);
-        if (relationshipsToAdd.isEmpty()) {
-            return false;
-        }
-
-        for (DbRelationship dr : relationshipsToAdd) {
-            addMissingRelationship(entity, dr);
-        }
-
-        return true;
-    }
-
-    private boolean createObjRelationship(ObjEntity entity, DbRelationship dr, String targetEntityName) {
-        String relationshipName = nameGenerator.createObjRelationshipName(dr);
-        relationshipName = DefaultUniqueNameGenerator.generate(NameCheckers.objRelationship, entity, relationshipName);
-
-        ObjRelationship or = new ObjRelationship(relationshipName);
-        or.addDbRelationship(dr);
-        Map<String, ObjEntity> objEntities = entity.getDataMap().getSubclassesForObjEntity(entity);
-
-        boolean hasFlattingAttributes = false;
-        boolean needGeneratedEntity = true;
-
-        if (objEntities.containsKey(targetEntityName)) {
-            needGeneratedEntity = false;
-        }
-
-        for (ObjEntity subObjEntity : objEntities.values()) {
-            for (ObjAttribute objAttribute : subObjEntity.getAttributes()) {
-                String path = objAttribute.getDbAttributePath();
-                if (path != null) {
-                    if (path.startsWith(or.getDbRelationshipPath())) {
-                        hasFlattingAttributes = true;
-                        break;
-                    }
-                }
-            }
-        }
-
-        if (!hasFlattingAttributes) {
-            if (needGeneratedEntity) {
-                or.setTargetEntityName(targetEntityName);
-                or.setSourceEntity(entity);
-            }
-
-            entity.addRelationship(or);
-            fireRelationshipAdded(or);
-        }
-
-        return needGeneratedEntity;
-    }
-
-    private boolean addMissingAttributes(ObjEntity entity) {
-        boolean changed = false;
-
-        for (DbAttribute da : getAttributesToAdd(entity)) {
-            addMissingAttribute(entity, da);
-            changed = true;
-        }
-        return changed;
-    }
-
-    private void addMissingRelationship(ObjEntity entity, DbRelationship dbRelationship) {
-        DbEntity targetEntity = dbRelationship.getTargetEntity();
-
-        Collection<ObjEntity> mappedObjEntities = map.getMappedEntities(targetEntity);
-        if (!mappedObjEntities.isEmpty()) {
-            for (Entity mappedTarget : mappedObjEntities) {
-                createObjRelationship(entity, dbRelationship, mappedTarget.getName());
-            }
-        } else {
-
-            if (targetEntity == null) {
-                targetEntity = new DbEntity(dbRelationship.getTargetEntityName());
-            }
-
-            if (dbRelationship.getTargetEntityName() != null) {
-                boolean needGeneratedEntity = createObjRelationship(entity, dbRelationship,
-                        nameGenerator.createObjEntityName(targetEntity));
-                if (needGeneratedEntity) {
-                    LOG.warn("Can't find ObjEntity for " + dbRelationship.getTargetEntityName());
-                    LOG.warn("Db Relationship (" + dbRelationship + ") will have GUESSED Obj Relationship reflection. ");
-                }
-            }
-        }
-    }
-
-    private void addMissingAttribute(ObjEntity entity, DbAttribute da) {
-        String attrName = DefaultUniqueNameGenerator.generate(NameCheckers.objAttribute, entity,
-                nameGenerator.createObjAttributeName(da));
-
-        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);
-    }
-
-    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;
-    }
-
-    /**
-     * Returns a list of DbAttributes that are mapped to foreign keys.
-     *
-     * @since 1.2
-     */
-    public Collection<DbAttribute> getMeaningfulFKs(ObjEntity objEntity) {
-        List<DbAttribute> fks = new ArrayList<DbAttribute>(2);
-
-        for (ObjAttribute property : objEntity.getAttributes()) {
-            DbAttribute column = property.getDbAttribute();
-
-            // check if adding it makes sense at all
-            if (column != null && column.isForeignKey()) {
-                fks.add(column);
-            }
-        }
-
-        return fks;
-    }
-
-    /**
-     * Returns a list of attributes that exist in the DbEntity, but are missing
-     * from the ObjEntity.
-     */
-    protected List<DbAttribute> getAttributesToAdd(ObjEntity objEntity) {
-        DbEntity dbEntity = objEntity.getDbEntity();
-
-        List<DbAttribute> missing = new ArrayList<DbAttribute>();
-        Collection<DbRelationship> incomingRels = getIncomingRelationships(dbEntity);
-
-        for (DbAttribute dba : dbEntity.getAttributes()) {
-
-            if (isMissingFromObjEntity(objEntity, dba, incomingRels)) {
-                missing.add(dba);
-            }
-        }
-
-        return missing;
-    }
-
-    protected boolean isMissingFromObjEntity(ObjEntity entity, DbAttribute dbAttribute, Collection<DbRelationship> incomingRels) {
-
-        if (dbAttribute.getName() == null || entity.getAttributeForDbAttribute(dbAttribute) != null) {
-            return false;
-        }
-
-        boolean removeMeaningfulPKs = removePK(dbAttribute.getEntity());
-        if (removeMeaningfulPKs && dbAttribute.isPrimaryKey()) {
-            return false;
-        }
-
-        // check FK's
-        boolean isFK = false;
-        Iterator<DbRelationship> rit = dbAttribute.getEntity().getRelationships().iterator();
-        while (!isFK && rit.hasNext()) {
-            DbRelationship rel = rit.next();
-            for (DbJoin join : rel.getJoins()) {
-                if (join.getSource() == dbAttribute) {
-                    isFK = true;
-                    break;
-                }
-            }
-        }
-
-        if (!removeMeaningfulPKs) {
-            if (!dbAttribute.isPrimaryKey() && isFK) {
-                return false;
-            }
-        } else {
-            if (isFK) {
-                return false;
-            }
-        }
-
-        // check incoming relationships
-        rit = incomingRels.iterator();
-        while (!isFK && rit.hasNext()) {
-            DbRelationship rel = rit.next();
-            for (DbJoin join : rel.getJoins()) {
-                if (join.getTarget() == dbAttribute) {
-                    isFK = true;
-                    break;
-                }
-            }
-        }
-
-        if (!removeMeaningfulPKs) {
-            if (!dbAttribute.isPrimaryKey() && isFK) {
-                return false;
-            }
-        } else {
-            if (isFK) {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    protected boolean isMissingFromObjEntity(ObjEntity entity, DbRelationship dbRelationship) {
-        return dbRelationship.getName() != null && entity.getRelationshipForDbRelationship(dbRelationship) == null;
-    }
-
-    private Collection<DbRelationship> getIncomingRelationships(DbEntity entity) {
-        Collection<DbRelationship> incoming = new ArrayList<DbRelationship>();
-
-        for (DbEntity nextEntity : entity.getDataMap().getDbEntities()) {
-            for (DbRelationship relationship : nextEntity.getRelationships()) {
-
-                // TODO: PERFORMANCE 'getTargetEntity' is generally slow, called
-                // in this iterator it is showing (e.g. in YourKit profiles)..
-                // perhaps use cheaper 'getTargetEntityName()' or even better -
-                // pre-cache all relationships by target entity to avoid O(n)
-                // search ?
-                // (need to profile to prove the difference)
-                if (entity == relationship.getTargetEntity()) {
-                    incoming.add(relationship);
-                }
-            }
-        }
-
-        return incoming;
-    }
-
-    protected List<DbRelationship> getRelationshipsToAdd(ObjEntity objEntity) {
-        List<DbRelationship> missing = new ArrayList<DbRelationship>();
-        for (DbRelationship dbRel : objEntity.getDbEntity().getRelationships()) {
-            if (isMissingFromObjEntity(objEntity, dbRel)) {
-                missing.add(dbRel);
-            }
-        }
-
-        return missing;
-    }
-
-    /**
-     * @since 1.2
-     */
-    public boolean isRemoveMeaningfulFKs() {
-        return removeMeaningfulFKs;
-    }
-
-    /**
-     * @since 1.2
-     */
-    public void setRemoveMeaningfulFKs(boolean removeMeaningfulFKs) {
-        this.removeMeaningfulFKs = removeMeaningfulFKs;
-    }
-
-    /**
-     * Registers new EntityMergeListener
-     */
-    public void addEntityMergeListener(EntityMergeListener listener) {
-        listeners.add(listener);
-    }
-
-    /**
-     * Unregisters an EntityMergeListener
-     */
-    public void removeEntityMergeListener(EntityMergeListener listener) {
-        listeners.remove(listener);
-    }
-
-    /**
-     * Returns registered listeners
-     */
-    public EntityMergeListener[] getEntityMergeListeners() {
-        return listeners.toArray(new EntityMergeListener[listeners.size()]);
-    }
-
-    /**
-     * Notifies all listeners that an ObjAttribute was added
-     */
-    protected void fireAttributeAdded(ObjAttribute attr) {
-        for (EntityMergeListener listener : listeners) {
-            listener.objAttributeAdded(attr);
-        }
-    }
-
-    /**
-     * Notifies all listeners that an ObjRelationship was added
-     */
-    protected void fireRelationshipAdded(ObjRelationship rel) {
-        for (EntityMergeListener listener : listeners) {
-            listener.objRelationshipAdded(rel);
-        }
-    }
-
-    /**
-     * @return naming strategy for reverse engineering
-     */
-    public ObjectNameGenerator getNameGenerator() {
-        return nameGenerator;
-    }
-
-    /**
-     * @since 4.0
-     */
-    public boolean isUsePrimitives() {
-        return usePrimitives;
-    }
-
-    /**
-     * @param usePrimitives
-     * @since 4.0
-     */
-    public void setUsePrimitives(boolean usePrimitives) {
-        this.usePrimitives = usePrimitives;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index 6d9a682..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderIT.java
+++ /dev/null
@@ -1,431 +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 org.apache.cayenne.access.loader.DbLoaderConfiguration;
-import org.apache.cayenne.access.loader.filters.FiltersConfig;
-import org.apache.cayenne.access.loader.filters.PatternFilter;
-import org.apache.cayenne.access.loader.filters.TableFilter;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.map.*;
-import org.apache.cayenne.unit.UnitDbAdapter;
-import org.apache.cayenne.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.sql.Types;
-import java.util.Collection;
-import java.util.List;
-
-import static org.junit.Assert.*;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class DbLoaderIT extends ServerCase {
-
-    public static final DbLoaderConfiguration CONFIG = new DbLoaderConfiguration();
-    @Inject
-    private ServerRuntime runtime;
-
-    @Inject
-    private DbAdapter adapter;
-
-    @Inject
-    private ServerCaseDataSourceFactory dataSourceFactory;
-
-    @Inject
-    private UnitDbAdapter accessStackAdapter;
-
-    private DbLoader loader;
-
-    @Before
-    public void setUp() throws Exception {
-        loader = new DbLoader(dataSourceFactory.getSharedDataSource().getConnection(), adapter, null);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        loader.getConnection().close();
-    }
-
-    @Test
-    public void testGetTableTypes() throws Exception {
-
-        List<?> tableTypes = loader.getTableTypes();
-
-        assertNotNull(tableTypes);
-
-        String tableLabel = adapter.tableTypeForTable();
-        if (tableLabel != null) {
-            assertTrue("Missing type for table '" + tableLabel + "' - " + tableTypes, tableTypes.contains(tableLabel));
-        }
-
-        String viewLabel = adapter.tableTypeForView();
-        if (viewLabel != null) {
-            assertTrue("Missing type for view '" + viewLabel + "' - " + tableTypes, tableTypes.contains(viewLabel));
-        }
-    }
-
-    @Test
-    public void testGetTables() throws Exception {
-
-        String tableLabel = adapter.tableTypeForTable();
-
-        List<DetectedDbEntity> tables = loader.createTableLoader(null, null, TableFilter.everything())
-                .getDbEntities(TableFilter.everything(), new String[]{tableLabel});
-
-        assertNotNull(tables);
-
-        boolean foundArtist = false;
-
-        for (DetectedDbEntity table : tables) {
-            if ("ARTIST".equalsIgnoreCase(table.getName())) {
-                foundArtist = true;
-                break;
-            }
-        }
-
-        assertTrue("'ARTIST' is missing from the table list: " + tables, foundArtist);
-    }
-
-    @Test
-    public void testGetTablesWithWrongCatalog() throws Exception {
-
-        DbLoaderConfiguration config = new DbLoaderConfiguration();
-        config.setFiltersConfig(
-                FiltersConfig.create("WRONG", null, TableFilter.everything(), PatternFilter.INCLUDE_NOTHING));
-        List<DetectedDbEntity> tables = loader
-                .createTableLoader("WRONG", null, TableFilter.everything())
-                    .getDbEntities(TableFilter.everything(), new String[]{adapter.tableTypeForTable()});
-
-        assertNotNull(tables);
-        assertTrue(tables.isEmpty());
-    }
-
-    @Test
-    public void testGetTablesWithWrongSchema() throws Exception {
-
-        DbLoaderConfiguration config = new DbLoaderConfiguration();
-        config.setFiltersConfig(
-                FiltersConfig.create(null, "WRONG", TableFilter.everything(), PatternFilter.INCLUDE_NOTHING));
-        List<DetectedDbEntity> tables = loader
-                .createTableLoader(null, "WRONG", TableFilter.everything())
-                .getDbEntities(TableFilter.everything(), new String[]{adapter.tableTypeForTable()});
-
-        assertNotNull(tables);
-        assertTrue(tables.isEmpty());
-    }
-
-    @Test
-    public void testLoadWithMeaningfulPK() throws Exception {
-
-        DataMap map = new DataMap();
-        String[] tableLabel = { adapter.tableTypeForTable() };
-
-        loader.setCreatingMeaningfulPK(true);
-
-        List<DbEntity> entities = loader
-                .createTableLoader(null, null, TableFilter.everything())
-                .loadDbEntities(map, CONFIG, tableLabel);
-
-        loader.loadObjEntities(map, CONFIG, entities);
-
-        ObjEntity artist = map.getObjEntity("Artist");
-        assertNotNull(artist);
-
-        ObjAttribute id = artist.getAttribute("artistId");
-        assertNotNull(id);
-    }
-
-    /**
-     * DataMap loading is in one big test method, since breaking it in
-     * individual tests would require multiple reads of metatdata which is
-     * extremely slow on some RDBMS (Sybase).
-     */
-    @Test
-    public void testLoad() throws Exception {
-
-        boolean supportsUnique = runtime.getDataDomain().getDataNodes().iterator().next().getAdapter()
-                .supportsUniqueConstraints();
-        boolean supportsLobs = accessStackAdapter.supportsLobs();
-        boolean supportsFK = accessStackAdapter.supportsFKConstraints();
-
-        DataMap map = new DataMap();
-        map.setDefaultPackage("foo.x");
-
-        String tableLabel = adapter.tableTypeForTable();
-
-        // *** TESTING THIS ***
-        List<DbEntity> entities = loader
-                .createTableLoader(null, null, TableFilter.everything())
-                .loadDbEntities(map, CONFIG, new String[]{adapter.tableTypeForTable()});
-
-
-        assertDbEntities(map);
-
-        if (supportsLobs) {
-            assertLobDbEntities(map);
-        }
-
-        // *** TESTING THIS ***
-        loader.loadDbRelationships(CONFIG, null, null, entities);
-
-        if (supportsFK) {
-            Collection<DbRelationship> rels = getDbEntity(map, "ARTIST").getRelationships();
-            assertNotNull(rels);
-            assertTrue(!rels.isEmpty());
-
-            // test one-to-one
-            rels = getDbEntity(map, "PAINTING").getRelationships();
-            assertNotNull(rels);
-
-            // find relationship to PAINTING_INFO
-            DbRelationship oneToOne = null;
-            for (DbRelationship rel : rels) {
-                if ("PAINTING_INFO".equalsIgnoreCase(rel.getTargetEntityName())) {
-                    oneToOne = rel;
-                    break;
-                }
-            }
-
-            assertNotNull("No relationship to PAINTING_INFO", oneToOne);
-            assertFalse("Relationship to PAINTING_INFO must be to-one", oneToOne.isToMany());
-            assertTrue("Relationship to PAINTING_INFO must be to-one", oneToOne.isToDependentPK());
-
-            // test UNIQUE only if FK is supported...
-            if (supportsUnique) {
-                assertUniqueConstraintsInRelationships(map);
-            }
-        }
-
-        // *** TESTING THIS ***
-        loader.setCreatingMeaningfulPK(false);
-        loader.loadObjEntities(map, CONFIG, entities);
-
-        assertObjEntities(map);
-
-        // now when the map is loaded, test
-        // various things
-        // selectively check how different types were processed
-        if (accessStackAdapter.supportsColumnTypeReengineering()) {
-            checkTypes(map);
-        }
-    }
-
-    private void assertUniqueConstraintsInRelationships(DataMap map) {
-        // unfortunately JDBC metadata doesn't provide info for UNIQUE
-        // constraints....
-        // cant reengineer them...
-
-        // find rel to TO_ONEFK1
-        /*
-         * Iterator it = getDbEntity(map,
-         * "TO_ONEFK2").getRelationships().iterator(); DbRelationship rel =
-         * (DbRelationship) it.next(); assertEquals("TO_ONEFK1",
-         * rel.getTargetEntityName());
-         * assertFalse("UNIQUE constraint was ignored...", rel.isToMany());
-         */
-    }
-
-    private void assertDbEntities(DataMap map) {
-        DbEntity dae = getDbEntity(map, "ARTIST");
-        assertNotNull("Null 'ARTIST' entity, other DbEntities: " + map.getDbEntityMap(), dae);
-        assertEquals("ARTIST", dae.getName().toUpperCase());
-
-        DbAttribute a = getDbAttribute(dae, "ARTIST_ID");
-        assertNotNull(a);
-        assertTrue(a.isPrimaryKey());
-        assertFalse(a.isGenerated());
-
-        if (adapter.supportsGeneratedKeys()) {
-            DbEntity bag = getDbEntity(map, "GENERATED_COLUMN_TEST");
-            DbAttribute id = getDbAttribute(bag, "GENERATED_COLUMN");
-            assertTrue(id.isPrimaryKey());
-            assertTrue(id.isGenerated());
-        }
-    }
-
-    private void assertObjEntities(DataMap map) {
-
-        boolean supportsLobs = accessStackAdapter.supportsLobs();
-        boolean supportsFK = accessStackAdapter.supportsFKConstraints();
-
-        ObjEntity ae = map.getObjEntity("Artist");
-        assertNotNull(ae);
-        assertEquals("Artist", ae.getName());
-
-        // assert primary key is not an attribute
-        assertNull(ae.getAttribute("artistId"));
-
-        if (supportsLobs) {
-            assertLobObjEntities(map);
-        }
-
-        if (supportsFK) {
-            Collection<?> rels1 = ae.getRelationships();
-            assertNotNull(rels1);
-            assertTrue(rels1.size() > 0);
-        }
-
-        assertEquals("foo.x.Artist", ae.getClassName());
-    }
-
-    private void assertLobDbEntities(DataMap map) {
-        DbEntity blobEnt = getDbEntity(map, "BLOB_TEST");
-        assertNotNull(blobEnt);
-        DbAttribute blobAttr = getDbAttribute(blobEnt, "BLOB_COL");
-        assertNotNull(blobAttr);
-        assertTrue(msgForTypeMismatch(Types.BLOB, blobAttr), Types.BLOB == blobAttr.getType()
-                || Types.LONGVARBINARY == blobAttr.getType());
-
-        DbEntity clobEnt = getDbEntity(map, "CLOB_TEST");
-        assertNotNull(clobEnt);
-        DbAttribute clobAttr = getDbAttribute(clobEnt, "CLOB_COL");
-        assertNotNull(clobAttr);
-        assertTrue(msgForTypeMismatch(Types.CLOB, clobAttr), Types.CLOB == clobAttr.getType()
-                || Types.LONGVARCHAR == clobAttr.getType());
-
-/*
-        DbEntity nclobEnt = getDbEntity(map, "NCLOB_TEST");
-        assertNotNull(nclobEnt);
-        DbAttribute nclobAttr = getDbAttribute(nclobEnt, "NCLOB_COL");
-        assertNotNull(nclobAttr);
-        assertTrue(msgForTypeMismatch(Types.NCLOB, nclobAttr), Types.NCLOB == nclobAttr.getType()
-                || Types.LONGVARCHAR == nclobAttr.getType());
-*/
-    }
-
-    private void assertLobObjEntities(DataMap map) {
-        ObjEntity blobEnt = map.getObjEntity("BlobTest");
-        assertNotNull(blobEnt);
-        // BLOBs should be mapped as byte[]
-        ObjAttribute blobAttr = blobEnt.getAttribute("blobCol");
-        assertNotNull("BlobTest.blobCol failed to doLoad", blobAttr);
-        assertEquals("byte[]", blobAttr.getType());
-
-
-        ObjEntity clobEnt = map.getObjEntity("ClobTest");
-        assertNotNull(clobEnt);
-        // CLOBs should be mapped as Strings by default
-        ObjAttribute clobAttr = clobEnt.getAttribute("clobCol");
-        assertNotNull(clobAttr);
-        assertEquals(String.class.getName(), clobAttr.getType());
-
-
-        ObjEntity nclobEnt = map.getObjEntity("NclobTest");
-        assertNotNull(nclobEnt);
-        // CLOBs should be mapped as Strings by default
-        ObjAttribute nclobAttr = nclobEnt.getAttribute("nclobCol");
-        assertNotNull(nclobAttr);
-        assertEquals(String.class.getName(), nclobAttr.getType());
-    }
-
-    private DbEntity getDbEntity(DataMap map, String name) {
-        DbEntity de = map.getDbEntity(name);
-        // sometimes table names get converted to lowercase
-        if (de == null) {
-            de = map.getDbEntity(name.toLowerCase());
-        }
-
-        return de;
-    }
-
-    private DbAttribute getDbAttribute(DbEntity ent, String name) {
-        DbAttribute da = ent.getAttribute(name);
-        // sometimes table names get converted to lowercase
-        if (da == null) {
-            da = ent.getAttribute(name.toLowerCase());
-        }
-
-        return da;
-    }
-
-    private DataMap originalMap() {
-        return runtime.getDataDomain().getDataNodes().iterator().next().getDataMaps().iterator().next();
-    }
-
-    /**
-     * Selectively check how different types were processed.
-     */
-    public void checkTypes(DataMap map) {
-        DbEntity dbe = getDbEntity(map, "PAINTING");
-        DbEntity floatTest = getDbEntity(map, "FLOAT_TEST");
-        DbEntity smallintTest = getDbEntity(map, "SMALLINT_TEST");
-        DbAttribute integerAttr = getDbAttribute(dbe, "PAINTING_ID");
-        DbAttribute decimalAttr = getDbAttribute(dbe, "ESTIMATED_PRICE");
-        DbAttribute varcharAttr = getDbAttribute(dbe, "PAINTING_TITLE");
-        DbAttribute floatAttr = getDbAttribute(floatTest, "FLOAT_COL");
-        DbAttribute smallintAttr = getDbAttribute(smallintTest, "SMALLINT_COL");
-
-        // check decimal
-        assertTrue(msgForTypeMismatch(Types.DECIMAL, decimalAttr), Types.DECIMAL == decimalAttr.getType()
-                || Types.NUMERIC == decimalAttr.getType());
-        assertEquals(2, decimalAttr.getScale());
-
-        // check varchar
-        assertEquals(msgForTypeMismatch(Types.VARCHAR, varcharAttr), Types.VARCHAR, varcharAttr.getType());
-        assertEquals(255, varcharAttr.getMaxLength());
-        // check integer
-        assertEquals(msgForTypeMismatch(Types.INTEGER, integerAttr), Types.INTEGER, integerAttr.getType());
-        // check float
-        assertTrue(msgForTypeMismatch(Types.FLOAT, floatAttr), Types.FLOAT == floatAttr.getType()
-                || Types.DOUBLE == floatAttr.getType() || Types.REAL == floatAttr.getType());
-
-        // check smallint
-        assertTrue(msgForTypeMismatch(Types.SMALLINT, smallintAttr), Types.SMALLINT == smallintAttr.getType()
-                || Types.INTEGER == smallintAttr.getType());
-    }
-
-    public void checkAllDBEntities(DataMap map) {
-
-        for (DbEntity origEnt : originalMap().getDbEntities()) {
-            DbEntity newEnt = map.getDbEntity(origEnt.getName());
-            for (DbAttribute origAttr : origEnt.getAttributes()) {
-                DbAttribute newAttr = newEnt.getAttribute(origAttr.getName());
-                assertNotNull("No matching DbAttribute for '" + origAttr.getName(), newAttr);
-                assertEquals(msgForTypeMismatch(origAttr, newAttr), origAttr.getType(), newAttr.getType());
-                // length and precision doesn't have to be the same
-                // it must be greater or equal
-                assertTrue(origAttr.getMaxLength() <= newAttr.getMaxLength());
-                assertTrue(origAttr.getScale() <= newAttr.getScale());
-            }
-        }
-    }
-
-    private static String msgForTypeMismatch(DbAttribute origAttr, DbAttribute newAttr) {
-        return msgForTypeMismatch(origAttr.getType(), 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 static String attrMismatch(String attrName, String msg) {
-        return "[Error loading attribute '" + attrName + "': " + msg + "]";
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderPartialIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderPartialIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderPartialIT.java
deleted file mode 100644
index 7514b99..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DbLoaderPartialIT.java
+++ /dev/null
@@ -1,118 +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 static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import java.util.Collection;
-
-import org.apache.cayenne.CayenneException;
-import org.apache.cayenne.access.loader.DefaultDbLoaderDelegate;
-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.unit.di.server.CayenneProjects;
-import org.apache.cayenne.unit.di.server.ServerCase;
-import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
-import org.apache.cayenne.unit.di.server.UseServerRuntime;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
-public class DbLoaderPartialIT extends ServerCase {
-
-    @Inject
-    private DbAdapter adapter;
-
-    @Inject
-    private ServerCaseDataSourceFactory dataSourceFactory;
-
-    private DbLoader loader;
-
-    @Before
-    public void setUp() throws Exception {
-        loader = new DbLoader(
-                dataSourceFactory.getSharedDataSource().getConnection(),
-                adapter,
-                new DefaultDbLoaderDelegate());
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        loader.getConnection().close();
-    }
-
-    /**
-     * Tests that FKs are properly loaded when the relationship source is not loaded. See
-     * CAY-479. This test will perform two reverse engineers. The second reverse engineer
-     * will skip two tables that share relationships with PAINTING. Relationships in
-     * ARTIST and GALLERY should remain unmodified, and all PAINTING relationships should
-     * be loaded.
-     */
-    @Test
-    public void testPartialLoad() throws Exception {
-
-        DataMap map = new DataMap();
-        String tableLabel = adapter.tableTypeForTable();
-
-        loader.loadDataMapFromDB(null, "%", new String[] {tableLabel}, map);
-
-        Collection<?> rels = getDbEntity(map, "ARTIST").getRelationships();
-        assertNotNull(rels);
-        int artistRels = rels.size();
-
-        rels = getDbEntity(map, "GALLERY").getRelationships();
-        assertNotNull(rels);
-        int galleryRels = rels.size();
-
-        rels = getDbEntity(map, "PAINTING").getRelationships();
-        assertNotNull(rels);
-        int paintingRels = rels.size();
-
-        loader.loadDataMapFromDB(null, "%", new String[] {
-            tableLabel
-        }, map);
-
-        rels = getDbEntity(map, "ARTIST").getRelationships();
-        assertNotNull(rels);
-        assertEquals(artistRels, rels.size());
-
-        rels = getDbEntity(map, "GALLERY").getRelationships();
-        assertNotNull(rels);
-        assertEquals(galleryRels, rels.size());
-
-        rels = getDbEntity(map, "PAINTING").getRelationships();
-        assertNotNull(rels);
-        assertEquals(paintingRels, rels.size());
-    }
-
-    private DbEntity getDbEntity(DataMap map, String name) {
-        DbEntity de = map.getDbEntity(name);
-        // sometimes table names get converted to lowercase
-        if (de == null) {
-            de = map.getDbEntity(name.toLowerCase());
-        }
-
-        return de;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index ad4238f..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/ManyToManyCandidateEntityTest.java
+++ /dev/null
@@ -1,113 +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.loader;
-
-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 org.junit.Before;
-import org.junit.Test;
-
-import java.net.URL;
-import java.util.ArrayList;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-public class ManyToManyCandidateEntityTest {
-
-    private DataMap map;
-
-    @Before
-    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);
-    }
-
-    @Test
-    public void testMatchingForManyToManyEntity() throws Exception {
-        ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
-
-        assertNotNull(ManyToManyCandidateEntity.build(manyToManyEntity));
-    }
-
-    @Test
-    public void testMatchingForNotManyToManyEntity() throws Exception {
-        ObjEntity entity = map.getObjEntity("Table1");
-
-        assertNull(ManyToManyCandidateEntity.build(entity));
-    }
-
-    @Test
-    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/2f7b1d53/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
deleted file mode 100644
index a43f890..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/FiltersConfigTest.java
+++ /dev/null
@@ -1,93 +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.loader.filters;
-
-import junit.framework.TestCase;
-
-import java.util.Collections;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-public class FiltersConfigTest extends TestCase {
-
-    public void testToString_01() {
-        FiltersConfig config = FiltersConfig.create(null, null,
-                TableFilter.everything(), PatternFilter.INCLUDE_EVERYTHING);
-
-        assertEquals("Catalog: null\n" +
-                     "  Schema: null\n" +
-                     "    Tables: \n" +
-                     "      Include: null Columns: ALL\n" +
-                     "    Procedures: ALL\n", config.toString());
-    }
-
-    public void testToString_02() {
-        FiltersConfig config = new FiltersConfig(
-                new CatalogFilter("catalog_01",
-                        new SchemaFilter("schema_11", TableFilter.everything(), PatternFilter.INCLUDE_EVERYTHING)),
-                new CatalogFilter("catalog_02",
-                        new SchemaFilter("schema_21", TableFilter.everything(), PatternFilter.INCLUDE_NOTHING),
-                        new SchemaFilter("schema_22",
-                                new TableFilter(
-                                        includes(new IncludeTableFilter(null, PatternFilter.INCLUDE_NOTHING)),
-                                        excludes("aaa")),
-                                PatternFilter.INCLUDE_NOTHING),
-                        new SchemaFilter("schema_23", TableFilter.include("include"), PatternFilter.INCLUDE_NOTHING)
-                )
-        );
-
-        assertEquals("Catalog: catalog_01\n" +
-                     "  Schema: schema_11\n" +
-                     "    Tables: \n" +
-                     "      Include: null Columns: ALL\n" +
-                     "    Procedures: ALL\n" +
-                     "Catalog: catalog_02\n" +
-                     "  Schema: schema_21\n" +
-                     "    Tables: \n" +
-                     "      Include: null Columns: ALL\n" +
-                     "    Procedures: NONE\n" +
-                     "  Schema: schema_22\n" +
-                     "    Tables: \n" +
-                     "      Include: null Columns: NONE\n" +
-                     "      aaa\n" +
-                     "    Procedures: NONE\n" +
-                     "  Schema: schema_23\n" +
-                     "    Tables: \n" +
-                     "      Include: include Columns: ALL\n" +
-                     "    Procedures: NONE\n", config.toString());
-    }
-
-    private SortedSet<Pattern> excludes(String ... p) {
-        SortedSet<Pattern> patterns = new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR);
-        for (String pattern : p) {
-            patterns.add(PatternFilter.pattern(pattern));
-        }
-        return patterns;
-    }
-
-    protected SortedSet<IncludeTableFilter> includes(IncludeTableFilter ... filters) {
-        SortedSet<IncludeTableFilter> includeTableFilters = new TreeSet<IncludeTableFilter>();
-        Collections.addAll(includeTableFilters, filters);
-
-        return includeTableFilters;
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/IncludeFilterTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/IncludeFilterTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/IncludeFilterTest.java
deleted file mode 100644
index c8cbe5b..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/IncludeFilterTest.java
+++ /dev/null
@@ -1,34 +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.loader.filters;
-
-import org.junit.Test;
-
-public class IncludeFilterTest {
-
-    @Test
-    public void testIsInclude() throws Exception {
-//        IncludeFilter filter = new IncludeFilter(pattern("^v_.*$"));
-//        assertTrue(filter.isInclude("v_new_view"));
-//        assertFalse(filter.isInclude("new_view"));
-//        assertFalse(filter.isInclude("view"));
-//        assertFalse(filter.isInclude("girl"));
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/PatternFilterTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/PatternFilterTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/PatternFilterTest.java
deleted file mode 100644
index 5bd07d4..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/PatternFilterTest.java
+++ /dev/null
@@ -1,78 +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.loader.filters;
-
-import junit.framework.TestCase;
-
-public class PatternFilterTest extends TestCase {
-
-    public void testInclude() throws Exception {
-        PatternFilter filter = new PatternFilter()
-                .include("aaa")
-                .include("bbb");
-
-        assertTrue(filter.isInclude("aaa"));
-        assertTrue(filter.isInclude("bbb"));
-        assertFalse(filter.isInclude("aaaa"));
-        assertFalse(filter.isInclude("aa"));
-        assertFalse(filter.isInclude("abb"));
-
-        filter = new PatternFilter().include("^v_.*$");
-        assertTrue(filter.isInclude("v_new_view"));
-        assertFalse(filter.isInclude("new_view"));
-        assertFalse(filter.isInclude("view"));
-        assertFalse(filter.isInclude("girl"));
-    }
-
-    public void testExclude() throws Exception {
-        PatternFilter filter = new PatternFilter()
-                .exclude("aaa")
-                .exclude("bbb");
-
-        assertFalse(filter.isInclude("aaa"));
-        assertFalse(filter.isInclude("bbb"));
-        assertTrue(filter.isInclude("aaaa"));
-        assertTrue(filter.isInclude("aa"));
-        assertTrue(filter.isInclude("abb"));
-    }
-
-    public void testIncludeExclude() throws Exception {
-        PatternFilter filter = new PatternFilter()
-                .include("aa.*")
-                .exclude("aaa");
-
-        assertFalse(filter.isInclude("aaa"));
-        assertFalse(filter.isInclude("bbb"));
-        assertTrue(filter.isInclude("aaaa"));
-        assertTrue(filter.isInclude("aa"));
-        assertFalse(filter.isInclude("abb"));
-    }
-
-    public void testIncludeAllFilter() {
-        assertTrue(PatternFilter.INCLUDE_EVERYTHING.isInclude("qwe"));
-        assertTrue(PatternFilter.INCLUDE_EVERYTHING.isInclude(""));
-        assertTrue(PatternFilter.INCLUDE_EVERYTHING.isInclude(null));
-    }
-
-    public void testIncludeNoneFilter() {
-        assertFalse(PatternFilter.INCLUDE_NOTHING.isInclude("qwe"));
-        assertFalse(PatternFilter.INCLUDE_NOTHING.isInclude(""));
-        assertFalse(PatternFilter.INCLUDE_NOTHING.isInclude(null));
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/TableFilterTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/TableFilterTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/TableFilterTest.java
deleted file mode 100644
index 4fad476..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/filters/TableFilterTest.java
+++ /dev/null
@@ -1,91 +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.loader.filters;
-
-import junit.framework.TestCase;
-
-import java.util.TreeSet;
-import java.util.regex.Pattern;
-
-public class TableFilterTest extends TestCase {
-
-    public void testIncludeEverything() {
-        TableFilter filter = TableFilter.everything();
-
-        assertNotNull(filter.isIncludeTable("table"));
-        assertNotNull(filter.isIncludeTable("aaaa"));
-        assertNotNull(filter.isIncludeTable(""));
-        assertNotNull(filter.isIncludeTable("alex"));
-    }
-
-    public void testInclude() {
-        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
-        includes.add(new IncludeTableFilter("aaa"));
-        includes.add(new IncludeTableFilter("bb"));
-
-        TableFilter filter = new TableFilter(includes, new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR));
-
-        assertNotNull(filter.isIncludeTable("aaa"));
-        assertNull(filter.isIncludeTable("aa"));
-        assertNull(filter.isIncludeTable("aaaa"));
-
-        assertNotNull(filter.isIncludeTable("bb"));
-        assertNull(filter.isIncludeTable(""));
-        assertNull(filter.isIncludeTable("bbbb"));
-    }
-
-
-    public void testExclude() {
-        TreeSet<Pattern> excludes = new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR);
-        excludes.add(Pattern.compile("aaa"));
-        excludes.add(Pattern.compile("bb"));
-
-        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
-        includes.add(new IncludeTableFilter(null, PatternFilter.INCLUDE_EVERYTHING));
-
-        TableFilter filter = new TableFilter(includes, excludes);
-
-        assertNull(filter.isIncludeTable("aaa"));
-        assertNotNull(filter.isIncludeTable("aa"));
-        assertNotNull(filter.isIncludeTable("aaaa"));
-
-        assertNull(filter.isIncludeTable("bb"));
-        assertNotNull(filter.isIncludeTable(""));
-        assertNotNull(filter.isIncludeTable("bbbb"));
-    }
-
-    public void testIncludeExclude() {
-        TreeSet<Pattern> excludes = new TreeSet<Pattern>(PatternFilter.PATTERN_COMPARATOR);
-        excludes.add(Pattern.compile("aaa"));
-        excludes.add(Pattern.compile("bb"));
-
-        TreeSet<IncludeTableFilter> includes = new TreeSet<IncludeTableFilter>();
-        includes.add(new IncludeTableFilter("aa.*"));
-
-        TableFilter filter = new TableFilter(includes, excludes);
-
-        assertNull(filter.isIncludeTable("aaa"));
-        assertNotNull(filter.isIncludeTable("aa"));
-        assertNotNull(filter.isIncludeTable("aaaa"));
-
-        assertNull(filter.isIncludeTable("bb"));
-        assertNull(filter.isIncludeTable(""));
-        assertNull(filter.isIncludeTable("bbbb"));
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/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
deleted file mode 100644
index 8b2e8d4..0000000
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/loader/mapper/DbTypeTest.java
+++ /dev/null
@@ -1,87 +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.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/2f7b1d53/cayenne-server/src/test/java/org/apache/cayenne/dba/PerAdapterProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/dba/PerAdapterProviderTest.java b/cayenne-server/src/test/java/org/apache/cayenne/dba/PerAdapterProviderTest.java
new file mode 100644
index 0000000..fb2ebcb
--- /dev/null
+++ b/cayenne-server/src/test/java/org/apache/cayenne/dba/PerAdapterProviderTest.java
@@ -0,0 +1,83 @@
+/*****************************************************************
+ *   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.dba;
+
+import org.apache.cayenne.access.types.ExtendedType;
+import org.apache.cayenne.access.types.ExtendedTypeFactory;
+import org.apache.cayenne.configuration.RuntimeProperties;
+import org.apache.cayenne.dba.derby.DerbyAdapter;
+import org.apache.cayenne.dba.oracle.OracleAdapter;
+import org.apache.cayenne.di.DIRuntimeException;
+import org.apache.cayenne.di.Provider;
+import org.apache.cayenne.di.spi.DefaultClassLoaderManager;
+import org.apache.cayenne.log.CommonsJdbcEventLogger;
+import org.apache.cayenne.resource.ClassLoaderResourceLocator;
+import org.apache.cayenne.resource.ResourceLocator;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class PerAdapterProviderTest {
+
+    private OracleAdapter oracleAdapter;
+    private DerbyAdapter derbyAdapter;
+    private AutoAdapter autoDerbyAdapter;
+
+    @Before
+    public void before() {
+
+        ResourceLocator locator = new ClassLoaderResourceLocator(new DefaultClassLoaderManager());
+        RuntimeProperties runtimeProperties = mock(RuntimeProperties.class);
+
+        this.oracleAdapter = new OracleAdapter(runtimeProperties,
+                Collections.<ExtendedType>emptyList(),
+                Collections.<ExtendedType>emptyList(),
+                Collections.<ExtendedTypeFactory>emptyList(),
+                locator);
+
+        this.derbyAdapter = new DerbyAdapter(runtimeProperties,
+                Collections.<ExtendedType>emptyList(),
+                Collections.<ExtendedType>emptyList(),
+                Collections.<ExtendedTypeFactory>emptyList(),
+                locator);
+
+        this.autoDerbyAdapter = new AutoAdapter(new Provider<DbAdapter>() {
+            @Override
+            public DbAdapter get() throws DIRuntimeException {
+                return derbyAdapter;
+            }
+        }, new CommonsJdbcEventLogger(runtimeProperties));
+    }
+
+    @Test
+    public void testGet() {
+
+        Map<String, String> map = Collections.singletonMap(DerbyAdapter.class.getName(), "x");
+        PerAdapterProvider<String> provider = new PerAdapterProvider<>(map, "default");
+
+        assertEquals("default", provider.get(oracleAdapter));
+        assertEquals("x", provider.get(derbyAdapter));
+        assertEquals("x", provider.get(autoDerbyAdapter));
+    }
+}


[14/15] cayenne git commit: CAY-2116 Split schema synchronization code in a separate module

Posted by aa...@apache.org.
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropColumnToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropColumnToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropColumnToModel.java
new file mode 100644
index 0000000..ceb03bc
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropColumnToModel.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.dbsync.merge;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * A {@link MergerToken} to remove a {@link DbAttribute} from a {@link DbEntity}.
+ * 
+ */
+public class DropColumnToModel extends AbstractToModelToken.EntityAndColumn {
+
+    public DropColumnToModel(DbEntity entity, DbAttribute column) {
+        super("Drop Column", entity, column);
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createAddColumnToDb(getEntity(), getColumn());
+    }
+
+    public void execute(MergerContext mergerContext) {
+
+        // remove relationships mapped to column. duplicate List to prevent
+        // ConcurrentModificationException
+        List<DbRelationship> dbRelationships = new ArrayList<DbRelationship>(getEntity()
+                .getRelationships());
+        for (DbRelationship dbRelationship : dbRelationships) {
+            for (DbJoin join : dbRelationship.getJoins()) {
+                if (join.getSource() == getColumn() || join.getTarget() == getColumn()) {
+                    remove(mergerContext.getModelMergeDelegate(), dbRelationship, true);
+                }
+            }
+        }
+
+        // remove ObjAttribute mapped to same column
+        for (ObjEntity objEntity : getEntity().mappedObjEntities()) {
+            ObjAttribute objAttribute = objEntity.getAttributeForDbAttribute(getColumn());
+            if (objAttribute != null) {
+                objEntity.removeAttribute(objAttribute.getName());
+                mergerContext.getModelMergeDelegate().objAttributeRemoved(objAttribute);
+            }
+
+        }
+
+        // remove DbAttribute
+        getEntity().removeAttribute(getColumn().getName());
+
+        mergerContext.getModelMergeDelegate().dbAttributeRemoved(getColumn());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToDb.java
new file mode 100644
index 0000000..238a44a
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToDb.java
@@ -0,0 +1,72 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.DbRelationshipDetected;
+
+public class DropRelationshipToDb extends AbstractToDbToken.Entity {
+
+    private DbRelationship rel;
+
+    public DropRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        super("Drop foreign key", entity);
+        this.rel = rel;
+    }
+    
+    public String getFkName() {
+        if (rel instanceof DbRelationshipDetected) {
+            return ((DbRelationshipDetected) rel).getFkName();
+        }
+        return null;
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        String fkName = getFkName();
+        if (fkName == null) {
+            return Collections.emptyList();
+        }
+
+        QuotingStrategy context = adapter.getQuotingStrategy();
+        return Collections.singletonList(
+                "ALTER TABLE " + context.quotedFullyQualifiedName(getEntity()) + " DROP CONSTRAINT " + fkName);
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createAddRelationshipToModel(getEntity(), rel);
+    }
+
+    @Override
+    public String getTokenValue() {
+        return rel.getSourceEntity().getName() + "->" + rel.getTargetEntityName();
+    }
+    
+    public DbRelationship getRelationship() {
+        return rel;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToModel.java
new file mode 100644
index 0000000..686df6e
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropRelationshipToModel.java
@@ -0,0 +1,51 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+public class DropRelationshipToModel extends AbstractToModelToken.Entity {
+
+    private final DbRelationship rel;
+
+    public DropRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        super("Drop db-relationship ", entity);
+        this.rel = rel;
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createAddRelationshipToDb(getEntity(), rel);
+    }
+
+    public void execute(MergerContext mergerContext) {
+        remove(mergerContext.getModelMergeDelegate(), rel, true);
+    }
+
+    @Override
+    public String getTokenValue() {
+        return AddRelationshipToModel.getTokenValue(rel);
+    }
+    
+    public DbRelationship getRelationship() {
+        return rel;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropTableToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropTableToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropTableToDb.java
new file mode 100644
index 0000000..6b76646
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropTableToDb.java
@@ -0,0 +1,50 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbEntity;
+
+public class DropTableToDb extends AbstractToDbToken.Entity {
+
+    public DropTableToDb(DbEntity entity) {
+        super("Drop Table", entity);
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        List<String> sqls = new ArrayList<String>();
+        // TODO: fix. some adapters drop the complete AUTO_PK_SUPPORT here
+        /*
+        sqls.addAll(adapter.getPkGenerator().dropAutoPkStatements(
+                Collections.singletonList(entity)));
+         */
+        sqls.addAll(adapter.dropTableStatements(getEntity()));
+        return sqls;
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createCreateTableToModel(getEntity());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropTableToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropTableToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropTableToModel.java
new file mode 100644
index 0000000..7a47f37
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DropTableToModel.java
@@ -0,0 +1,49 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * A {@link MergerToken} to remove a {@link DbEntity} from a {@link DataMap}. Any
+ * {@link ObjEntity} mapped to the {@link DbEntity} will also be removed.
+ * 
+ */
+public class DropTableToModel extends AbstractToModelToken.Entity {
+
+    public DropTableToModel(DbEntity entity) {
+        super("Drop Table", entity);
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createCreateTableToDb(getEntity());
+    }
+
+    public void execute(MergerContext mergerContext) {
+        for (ObjEntity objEntity : getEntity().mappedObjEntities()) {
+            objEntity.getDataMap().removeObjEntity(objEntity.getName(), true);
+            mergerContext.getModelMergeDelegate().objEntityRemoved(objEntity);
+        }
+        getEntity().getDataMap().removeDbEntity(getEntity().getName(), true);
+        mergerContext.getModelMergeDelegate().dbEntityRemoved(getEntity());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DummyReverseToken.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DummyReverseToken.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DummyReverseToken.java
new file mode 100644
index 0000000..54b779b
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/DummyReverseToken.java
@@ -0,0 +1,60 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+
+/**
+ * The reverse of a {@link MergerToken} that can not be reversed.. This will not execute
+ * any thing, but {@link #createReverse(MergerTokenFactory)} will get back the reverse that
+ * this was made from.
+ */
+class DummyReverseToken implements MergerToken {
+
+    private MergerToken reverse;
+
+    public DummyReverseToken(MergerToken reverse) {
+        this.reverse = reverse;
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return reverse;
+    }
+
+    public void execute(MergerContext mergerContext) {
+        // can not execute
+    }
+
+    public MergeDirection getDirection() {
+        return reverse.getDirection().reverseDirection();
+    }
+
+    public String getTokenName() {
+        return "Can not execute the reverse of " + reverse.getTokenName();
+    }
+
+    public String getTokenValue() {
+        return reverse.getTokenValue();
+    }
+
+    public boolean isReversible() {
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EmptyValueForNullProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EmptyValueForNullProvider.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EmptyValueForNullProvider.java
new file mode 100644
index 0000000..a484655
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EmptyValueForNullProvider.java
@@ -0,0 +1,40 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * A dummy {@link ValueForNullProvider} that are not able to provide any values
+ */
+class EmptyValueForNullProvider implements ValueForNullProvider {
+
+    public List<String> createSql(DbEntity entity, DbAttribute column) {
+        return Collections.emptyList();
+    }
+
+    public boolean hasValueFor(DbEntity entity, DbAttribute column) {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EntityMergeSupport.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EntityMergeSupport.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EntityMergeSupport.java
new file mode 100644
index 0000000..8aea02a
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/EntityMergeSupport.java
@@ -0,0 +1,522 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.naming.DefaultUniqueNameGenerator;
+import org.apache.cayenne.map.naming.LegacyNameGenerator;
+import org.apache.cayenne.map.naming.NameCheckers;
+import org.apache.cayenne.map.naming.ObjectNameGenerator;
+import org.apache.cayenne.util.DeleteRuleUpdater;
+import org.apache.cayenne.util.EntityMergeListener;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implements methods for entity merging.
+ */
+public class EntityMergeSupport {
+
+    private static final Log LOG = LogFactory.getLog(EntityMergeSupport.class);
+
+    private static final Map<String, String> CLASS_TO_PRIMITIVE;
+
+    static {
+        CLASS_TO_PRIMITIVE = new HashMap<>();
+        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");
+    }
+
+    private final DataMap map;
+    /**
+     * Strategy for choosing names for entities, attributes and relationships
+     */
+    private final ObjectNameGenerator nameGenerator;
+    /**
+     * Listeners of merge process.
+     */
+    private final List<EntityMergeListener> listeners = new ArrayList<EntityMergeListener>();
+    protected boolean removeMeaningfulFKs;
+    protected boolean removeMeaningfulPKs;
+    protected boolean usePrimitives;
+
+    public EntityMergeSupport(DataMap map) {
+        this(map, new LegacyNameGenerator(), true);
+    }
+
+    /**
+     * @since 3.0
+     */
+    public EntityMergeSupport(DataMap map, ObjectNameGenerator nameGenerator, boolean removeMeaningfulPKs) {
+        this.map = map;
+        this.nameGenerator = nameGenerator;
+        this.removeMeaningfulFKs = true;
+        this.removeMeaningfulPKs = removeMeaningfulPKs;
+
+        /**
+         * Adding a listener, so that all created ObjRelationships would have
+         * default delete rule
+         */
+        addEntityMergeListener(DeleteRuleUpdater.getEntityMergeListener());
+    }
+
+    /**
+     * Updates each one of the collection of ObjEntities, adding attributes and
+     * relationships based on the current state of its DbEntity.
+     *
+     * @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(Iterable<ObjEntity> objEntities) {
+        boolean changed = false;
+        for (ObjEntity nextEntity : objEntities) {
+            if (synchronizeWithDbEntity(nextEntity)) {
+                changed = true;
+            }
+        }
+
+        return changed;
+    }
+
+    /**
+     * @since 4.0
+     */
+    protected boolean removePK(DbEntity dbEntity) {
+        return removeMeaningfulPKs;
+    }
+
+    /**
+     * @since 4.0
+     */
+    protected boolean removeFK(DbEntity dbEntity) {
+        return removeMeaningfulFKs;
+    }
+
+    /**
+     * Updates ObjEntity attributes and relationships based on the current state
+     * of its DbEntity.
+     *
+     * @return true if the ObjEntity has changed as a result of synchronization.
+     */
+    public boolean synchronizeWithDbEntity(ObjEntity entity) {
+
+        if (entity == null) {
+            return false;
+        }
+
+        DbEntity dbEntity = entity.getDbEntity();
+        if (dbEntity == null) {
+            return false;
+        }
+
+        boolean changed = false;
+
+        // synchronization on DataMap is some (weak) protection
+        // against simultaneous modification of the map (like double-clicking on sync button)
+        synchronized (map) {
+
+            if (removeFK(dbEntity)) {
+                changed = getRidOfAttributesThatAreNowSrcAttributesForRelationships(entity);
+            }
+
+            changed |= addMissingAttributes(entity);
+            changed |= addMissingRelationships(entity);
+        }
+
+        return changed;
+    }
+
+    /**
+     * @since 4.0
+     */
+    public boolean synchronizeOnDbAttributeAdded(ObjEntity entity, DbAttribute dbAttribute) {
+
+        Collection<DbRelationship> incomingRels = getIncomingRelationships(dbAttribute.getEntity());
+        if (isMissingFromObjEntity(entity, dbAttribute, incomingRels)) {
+            addMissingAttribute(entity, dbAttribute);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @since 4.0
+     */
+    public boolean synchronizeOnDbRelationshipAdded(ObjEntity entity, DbRelationship dbRelationship) {
+
+        if (isMissingFromObjEntity(entity, dbRelationship)) {
+            addMissingRelationship(entity, dbRelationship);
+        }
+
+        return true;
+    }
+
+    private boolean addMissingRelationships(ObjEntity entity) {
+        List<DbRelationship> relationshipsToAdd = getRelationshipsToAdd(entity);
+        if (relationshipsToAdd.isEmpty()) {
+            return false;
+        }
+
+        for (DbRelationship dr : relationshipsToAdd) {
+            addMissingRelationship(entity, dr);
+        }
+
+        return true;
+    }
+
+    private boolean createObjRelationship(ObjEntity entity, DbRelationship dr, String targetEntityName) {
+        String relationshipName = nameGenerator.createObjRelationshipName(dr);
+        relationshipName = DefaultUniqueNameGenerator.generate(NameCheckers.objRelationship, entity, relationshipName);
+
+        ObjRelationship or = new ObjRelationship(relationshipName);
+        or.addDbRelationship(dr);
+        Map<String, ObjEntity> objEntities = entity.getDataMap().getSubclassesForObjEntity(entity);
+
+        boolean hasFlattingAttributes = false;
+        boolean needGeneratedEntity = true;
+
+        if (objEntities.containsKey(targetEntityName)) {
+            needGeneratedEntity = false;
+        }
+
+        for (ObjEntity subObjEntity : objEntities.values()) {
+            for (ObjAttribute objAttribute : subObjEntity.getAttributes()) {
+                String path = objAttribute.getDbAttributePath();
+                if (path != null) {
+                    if (path.startsWith(or.getDbRelationshipPath())) {
+                        hasFlattingAttributes = true;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (!hasFlattingAttributes) {
+            if (needGeneratedEntity) {
+                or.setTargetEntityName(targetEntityName);
+                or.setSourceEntity(entity);
+            }
+
+            entity.addRelationship(or);
+            fireRelationshipAdded(or);
+        }
+
+        return needGeneratedEntity;
+    }
+
+    private boolean addMissingAttributes(ObjEntity entity) {
+        boolean changed = false;
+
+        for (DbAttribute da : getAttributesToAdd(entity)) {
+            addMissingAttribute(entity, da);
+            changed = true;
+        }
+        return changed;
+    }
+
+    private void addMissingRelationship(ObjEntity entity, DbRelationship dbRelationship) {
+        DbEntity targetEntity = dbRelationship.getTargetEntity();
+
+        Collection<ObjEntity> mappedObjEntities = map.getMappedEntities(targetEntity);
+        if (!mappedObjEntities.isEmpty()) {
+            for (Entity mappedTarget : mappedObjEntities) {
+                createObjRelationship(entity, dbRelationship, mappedTarget.getName());
+            }
+        } else {
+
+            if (targetEntity == null) {
+                targetEntity = new DbEntity(dbRelationship.getTargetEntityName());
+            }
+
+            if (dbRelationship.getTargetEntityName() != null) {
+                boolean needGeneratedEntity = createObjRelationship(entity, dbRelationship,
+                        nameGenerator.createObjEntityName(targetEntity));
+                if (needGeneratedEntity) {
+                    LOG.warn("Can't find ObjEntity for " + dbRelationship.getTargetEntityName());
+                    LOG.warn("Db Relationship (" + dbRelationship + ") will have GUESSED Obj Relationship reflection. ");
+                }
+            }
+        }
+    }
+
+    private void addMissingAttribute(ObjEntity entity, DbAttribute da) {
+        String attrName = DefaultUniqueNameGenerator.generate(NameCheckers.objAttribute, entity,
+                nameGenerator.createObjAttributeName(da));
+
+        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);
+    }
+
+    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;
+    }
+
+    /**
+     * Returns a list of DbAttributes that are mapped to foreign keys.
+     *
+     * @since 1.2
+     */
+    public Collection<DbAttribute> getMeaningfulFKs(ObjEntity objEntity) {
+        List<DbAttribute> fks = new ArrayList<DbAttribute>(2);
+
+        for (ObjAttribute property : objEntity.getAttributes()) {
+            DbAttribute column = property.getDbAttribute();
+
+            // check if adding it makes sense at all
+            if (column != null && column.isForeignKey()) {
+                fks.add(column);
+            }
+        }
+
+        return fks;
+    }
+
+    /**
+     * Returns a list of attributes that exist in the DbEntity, but are missing
+     * from the ObjEntity.
+     */
+    protected List<DbAttribute> getAttributesToAdd(ObjEntity objEntity) {
+        DbEntity dbEntity = objEntity.getDbEntity();
+
+        List<DbAttribute> missing = new ArrayList<DbAttribute>();
+        Collection<DbRelationship> incomingRels = getIncomingRelationships(dbEntity);
+
+        for (DbAttribute dba : dbEntity.getAttributes()) {
+
+            if (isMissingFromObjEntity(objEntity, dba, incomingRels)) {
+                missing.add(dba);
+            }
+        }
+
+        return missing;
+    }
+
+    protected boolean isMissingFromObjEntity(ObjEntity entity, DbAttribute dbAttribute, Collection<DbRelationship> incomingRels) {
+
+        if (dbAttribute.getName() == null || entity.getAttributeForDbAttribute(dbAttribute) != null) {
+            return false;
+        }
+
+        boolean removeMeaningfulPKs = removePK(dbAttribute.getEntity());
+        if (removeMeaningfulPKs && dbAttribute.isPrimaryKey()) {
+            return false;
+        }
+
+        // check FK's
+        boolean isFK = false;
+        Iterator<DbRelationship> rit = dbAttribute.getEntity().getRelationships().iterator();
+        while (!isFK && rit.hasNext()) {
+            DbRelationship rel = rit.next();
+            for (DbJoin join : rel.getJoins()) {
+                if (join.getSource() == dbAttribute) {
+                    isFK = true;
+                    break;
+                }
+            }
+        }
+
+        if (!removeMeaningfulPKs) {
+            if (!dbAttribute.isPrimaryKey() && isFK) {
+                return false;
+            }
+        } else {
+            if (isFK) {
+                return false;
+            }
+        }
+
+        // check incoming relationships
+        rit = incomingRels.iterator();
+        while (!isFK && rit.hasNext()) {
+            DbRelationship rel = rit.next();
+            for (DbJoin join : rel.getJoins()) {
+                if (join.getTarget() == dbAttribute) {
+                    isFK = true;
+                    break;
+                }
+            }
+        }
+
+        if (!removeMeaningfulPKs) {
+            if (!dbAttribute.isPrimaryKey() && isFK) {
+                return false;
+            }
+        } else {
+            if (isFK) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    protected boolean isMissingFromObjEntity(ObjEntity entity, DbRelationship dbRelationship) {
+        return dbRelationship.getName() != null && entity.getRelationshipForDbRelationship(dbRelationship) == null;
+    }
+
+    private Collection<DbRelationship> getIncomingRelationships(DbEntity entity) {
+        Collection<DbRelationship> incoming = new ArrayList<DbRelationship>();
+
+        for (DbEntity nextEntity : entity.getDataMap().getDbEntities()) {
+            for (DbRelationship relationship : nextEntity.getRelationships()) {
+
+                // TODO: PERFORMANCE 'getTargetEntity' is generally slow, called
+                // in this iterator it is showing (e.g. in YourKit profiles)..
+                // perhaps use cheaper 'getTargetEntityName()' or even better -
+                // pre-cache all relationships by target entity to avoid O(n)
+                // search ?
+                // (need to profile to prove the difference)
+                if (entity == relationship.getTargetEntity()) {
+                    incoming.add(relationship);
+                }
+            }
+        }
+
+        return incoming;
+    }
+
+    protected List<DbRelationship> getRelationshipsToAdd(ObjEntity objEntity) {
+        List<DbRelationship> missing = new ArrayList<DbRelationship>();
+        for (DbRelationship dbRel : objEntity.getDbEntity().getRelationships()) {
+            if (isMissingFromObjEntity(objEntity, dbRel)) {
+                missing.add(dbRel);
+            }
+        }
+
+        return missing;
+    }
+
+    /**
+     * @since 1.2
+     */
+    public boolean isRemoveMeaningfulFKs() {
+        return removeMeaningfulFKs;
+    }
+
+    /**
+     * @since 1.2
+     */
+    public void setRemoveMeaningfulFKs(boolean removeMeaningfulFKs) {
+        this.removeMeaningfulFKs = removeMeaningfulFKs;
+    }
+
+    /**
+     * Registers new EntityMergeListener
+     */
+    public void addEntityMergeListener(EntityMergeListener listener) {
+        listeners.add(listener);
+    }
+
+    /**
+     * Unregisters an EntityMergeListener
+     */
+    public void removeEntityMergeListener(EntityMergeListener listener) {
+        listeners.remove(listener);
+    }
+
+    /**
+     * Returns registered listeners
+     */
+    public EntityMergeListener[] getEntityMergeListeners() {
+        return listeners.toArray(new EntityMergeListener[listeners.size()]);
+    }
+
+    /**
+     * Notifies all listeners that an ObjAttribute was added
+     */
+    protected void fireAttributeAdded(ObjAttribute attr) {
+        for (EntityMergeListener listener : listeners) {
+            listener.objAttributeAdded(attr);
+        }
+    }
+
+    /**
+     * Notifies all listeners that an ObjRelationship was added
+     */
+    protected void fireRelationshipAdded(ObjRelationship rel) {
+        for (EntityMergeListener listener : listeners) {
+            listener.objRelationshipAdded(rel);
+        }
+    }
+
+    /**
+     * @return naming strategy for reverse engineering
+     */
+    public ObjectNameGenerator getNameGenerator() {
+        return nameGenerator;
+    }
+
+    /**
+     * @since 4.0
+     */
+    public boolean isUsePrimitives() {
+        return usePrimitives;
+    }
+
+    /**
+     * @param usePrimitives
+     * @since 4.0
+     */
+    public void setUsePrimitives(boolean usePrimitives) {
+        this.usePrimitives = usePrimitives;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergeDirection.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergeDirection.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergeDirection.java
new file mode 100644
index 0000000..4ac25e4
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergeDirection.java
@@ -0,0 +1,70 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+/**
+ * Represent a merge direction that can be either from the model to the db or from the db to the model.
+ */
+public enum MergeDirection {
+
+    /**
+     * TO_DB Token means that changes was made in object model and should be reflected at DB
+     */
+    TO_DB("To DB"),
+
+    /**
+     * TO_MODEL Token represent database changes that should be allayed to object model
+     */
+    TO_MODEL("To Model");
+
+    private String name;
+
+    MergeDirection(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isToDb() {
+        return (this == TO_DB);
+    }
+
+    public boolean isToModel() {
+        return (this == TO_MODEL);
+    }
+
+    @Override
+    public String toString() {
+        return getName();
+    }
+
+    public MergeDirection reverseDirection() {
+        switch (this) {
+            case TO_DB:
+                return TO_MODEL;
+            case TO_MODEL:
+                return TO_DB;
+            default:
+                throw new IllegalStateException("Invalid direction: " + this);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergerContext.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergerContext.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergerContext.java
new file mode 100644
index 0000000..772de97
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergerContext.java
@@ -0,0 +1,118 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.validation.ValidationResult;
+
+import javax.sql.DataSource;
+import java.util.Objects;
+
+/**
+ * An object passed as an argument to {@link MergerToken#execute(MergerContext)}s that a
+ * {@link MergerToken} can do its work.
+ */
+public class MergerContext {
+
+    private DataMap dataMap;
+    private DataNode dataNode;
+    private ValidationResult validationResult;
+    private ModelMergeDelegate delegate;
+
+    protected MergerContext() {
+    }
+
+    public static Builder builder(DataMap dataMap) {
+        return new Builder().dataMap(dataMap);
+    }
+
+    /**
+     * @deprecated since 4.0 use {@link #getDataNode()} and its {@link DataNode#getAdapter()} method.
+     */
+    @Deprecated
+    public DbAdapter getAdapter() {
+        return getDataNode().getAdapter();
+    }
+
+    /**
+     * Returns the DataMap that is the target of a the merge operation.
+     *
+     * @return the DataMap that is the target of a the merge operation.
+     */
+    public DataMap getDataMap() {
+        return dataMap;
+    }
+
+    public DataNode getDataNode() {
+        return dataNode;
+    }
+
+    public ValidationResult getValidationResult() {
+        return validationResult;
+    }
+
+    /**
+     * Returns a callback object that is invoked as the merge proceeds through tokens, modifying the DataMap.
+     *
+     * @return a callback object that is invoked as the merge proceeds through tokens, modifying the DataMap.
+     */
+    public ModelMergeDelegate getModelMergeDelegate() {
+        return delegate;
+    }
+
+    public static class Builder {
+
+        private MergerContext context;
+
+        private Builder() {
+            this.context = new MergerContext();
+            this.context.validationResult = new ValidationResult();
+            this.context.delegate = new DefaultModelMergeDelegate();
+            this.context.dataNode = new DataNode();
+        }
+
+        public MergerContext build() {
+            return context;
+        }
+
+        public Builder delegate(ModelMergeDelegate delegate) {
+            context.delegate = Objects.requireNonNull(delegate);
+            return this;
+        }
+
+        public Builder dataNode(DataNode dataNode) {
+            this.context.dataNode = Objects.requireNonNull(dataNode);
+            return this;
+        }
+
+        public Builder syntheticDataNode(DataSource dataSource, DbAdapter adapter) {
+            DataNode dataNode = new DataNode();
+            dataNode.setDataSource(dataSource);
+            dataNode.setAdapter(adapter);
+            return dataNode(dataNode);
+        }
+
+        public Builder dataMap(DataMap dataMap) {
+            context.dataMap = Objects.requireNonNull(dataMap);
+            return this;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergerToken.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergerToken.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergerToken.java
new file mode 100644
index 0000000..5359b62
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/MergerToken.java
@@ -0,0 +1,53 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+
+/**
+ * Represents a minimal atomic synchronization operation between database and Cayenne model.
+ */
+public interface MergerToken {
+
+    String getTokenName();
+
+    String getTokenValue();
+
+    /**
+     * The direction of this token. One of {@link MergeDirection#TO_DB} or
+     * {@link MergeDirection#TO_MODEL}
+     */
+    MergeDirection getDirection();
+
+    /**
+     * Create a complimentary token with the reverse direction. AddColumn in one direction becomes
+     * DropColumn in the other direction.
+     * <p>
+     * Not all tokens are reversible.
+     */
+    MergerToken createReverse(MergerTokenFactory factory);
+
+    /**
+     * Executes synchronization operation.
+     *
+     * @param mergerContext operation context.
+     */
+    void execute(MergerContext mergerContext);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ModelMergeDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ModelMergeDelegate.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ModelMergeDelegate.java
new file mode 100644
index 0000000..13f6ea5
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ModelMergeDelegate.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.dbsync.merge;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+
+/**
+ * A interface used to tell about modifications performed on the model by
+ * {@link MergerToken} with {@link MergeDirection#TO_MODEL}
+ */
+public interface ModelMergeDelegate {
+
+    public void dbEntityAdded(DbEntity ent);
+
+    public void dbEntityRemoved(DbEntity ent);
+
+    public void objEntityAdded(ObjEntity ent);
+
+    public void objEntityRemoved(ObjEntity ent);
+
+    public void dbAttributeAdded(DbAttribute att);
+
+    public void dbAttributeRemoved(DbAttribute att);
+
+    public void dbAttributeModified(DbAttribute att);
+
+    public void objAttributeAdded(ObjAttribute att);
+
+    public void objAttributeRemoved(ObjAttribute att);
+
+    public void objAttributeModified(ObjAttribute att);
+
+    public void dbRelationshipAdded(DbRelationship rel);
+
+    public void dbRelationshipRemoved(DbRelationship rel);
+
+    public void objRelationshipAdded(ObjRelationship rel);
+
+    public void objRelationshipRemoved(ObjRelationship rel);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ProxyModelMergeDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ProxyModelMergeDelegate.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ProxyModelMergeDelegate.java
new file mode 100644
index 0000000..ba12824
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ProxyModelMergeDelegate.java
@@ -0,0 +1,108 @@
+/*
+ * 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.dbsync.merge;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+
+/**
+ * @since 4.0
+ */
+public class ProxyModelMergeDelegate implements ModelMergeDelegate {
+
+    private final ModelMergeDelegate delegate;
+
+    public ProxyModelMergeDelegate(ModelMergeDelegate delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public void dbEntityAdded(DbEntity ent) {
+        delegate.dbEntityAdded(ent);
+    }
+
+    @Override
+    public void dbEntityRemoved(DbEntity ent) {
+        delegate.dbEntityRemoved(ent);
+    }
+
+    @Override
+    public void objEntityAdded(ObjEntity ent) {
+        delegate.objEntityAdded(ent);
+    }
+
+    @Override
+    public void objEntityRemoved(ObjEntity ent) {
+        delegate.objEntityRemoved(ent);
+    }
+
+    @Override
+    public void dbAttributeAdded(DbAttribute att) {
+        delegate.dbAttributeAdded(att);
+    }
+
+    @Override
+    public void dbAttributeRemoved(DbAttribute att) {
+        delegate.dbAttributeRemoved(att);
+    }
+
+    @Override
+    public void dbAttributeModified(DbAttribute att) {
+        delegate.dbAttributeModified(att);
+    }
+
+    @Override
+    public void objAttributeAdded(ObjAttribute att) {
+        delegate.objAttributeAdded(att);
+    }
+
+    @Override
+    public void objAttributeRemoved(ObjAttribute att) {
+        delegate.objAttributeRemoved(att);
+    }
+
+    @Override
+    public void objAttributeModified(ObjAttribute att) {
+        delegate.objAttributeModified(att);
+    }
+
+    @Override
+    public void dbRelationshipAdded(DbRelationship rel) {
+        delegate.dbRelationshipAdded(rel);
+    }
+
+    @Override
+    public void dbRelationshipRemoved(DbRelationship rel) {
+        delegate.dbRelationshipRemoved(rel);
+    }
+
+    @Override
+    public void objRelationshipAdded(ObjRelationship rel) {
+        delegate.objRelationshipAdded(rel);
+    }
+
+    @Override
+    public void objRelationshipRemoved(ObjRelationship rel) {
+        delegate.objRelationshipRemoved(rel);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDb.java
new file mode 100644
index 0000000..c69fb6f
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDb.java
@@ -0,0 +1,57 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * A {@link MergerToken} to add a "allow null" clause to a column.
+ * 
+ */
+public class SetAllowNullToDb extends AbstractToDbToken.EntityAndColumn {
+
+    public SetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        super("Set Allow Null", entity, column);
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        StringBuilder sqlBuffer = new StringBuilder();
+        QuotingStrategy context = adapter.getQuotingStrategy();
+        sqlBuffer.append("ALTER TABLE ");
+        sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+        sqlBuffer.append(" ALTER COLUMN ");
+        sqlBuffer.append(context.quotedName(getColumn()));
+        sqlBuffer.append(" DROP NOT NULL");
+
+        return Collections.singletonList(sqlBuffer.toString());
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetNotNullToModel(getEntity(), getColumn());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetAllowNullToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetAllowNullToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetAllowNullToModel.java
new file mode 100644
index 0000000..71a0937
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetAllowNullToModel.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * A {@link MergerToken} to set the mandatory field of a {@link DbAttribute} to false
+ * 
+ */
+public class SetAllowNullToModel extends AbstractToModelToken.EntityAndColumn {
+
+    public SetAllowNullToModel(DbEntity entity, DbAttribute column) {
+        super("Set Allow Null", entity, column);
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetNotNullToDb(getEntity(), getColumn());
+    }
+
+    public void execute(MergerContext mergerContext) {
+        getColumn().setMandatory(false);
+        mergerContext.getModelMergeDelegate().dbAttributeModified(getColumn());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetColumnTypeToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetColumnTypeToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetColumnTypeToDb.java
new file mode 100644
index 0000000..74a0034
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetColumnTypeToDb.java
@@ -0,0 +1,119 @@
+/*
+ * 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.dbsync.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+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.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * An {@link MergerToken} to use to set type, length and precision.
+ */
+public class SetColumnTypeToDb extends AbstractToDbToken.Entity {
+
+    private DbAttribute columnOriginal;
+    private DbAttribute columnNew;
+
+    public SetColumnTypeToDb(DbEntity entity, DbAttribute columnOriginal, DbAttribute columnNew) {
+        super("Set Column Type", entity);
+        this.columnOriginal = columnOriginal;
+        this.columnNew = columnNew;
+    }
+    
+    /**
+     * append the part of the token before the actual column data type
+     * @param context 
+     */
+    protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+        sqlBuffer.append("ALTER TABLE ");
+        sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+        sqlBuffer.append(" ALTER ");
+        sqlBuffer.append(context.quotedName(columnNew));
+        sqlBuffer.append(" TYPE ");
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        StringBuffer sqlBuffer = new StringBuffer();
+        appendPrefix(sqlBuffer, adapter.getQuotingStrategy());
+  
+        sqlBuffer.append(JdbcAdapter.getType(adapter, columnNew));
+        sqlBuffer.append(JdbcAdapter.sizeAndPrecision(adapter, columnNew));
+
+        return Collections.singletonList(sqlBuffer.toString());
+    }
+
+    @Override
+    public String getTokenValue() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getEntity().getName());
+        sb.append(".");
+        sb.append(columnNew.getName());
+
+        if (columnOriginal.getType() != columnNew.getType()) {
+            sb.append(" type: ");
+            sb.append(TypesMapping.getSqlNameByType(columnOriginal.getType()));
+            sb.append(" -> ");
+            sb.append(TypesMapping.getSqlNameByType(columnNew.getType()));
+        }
+
+        if (columnOriginal.getMaxLength() != columnNew.getMaxLength()) {
+            sb.append(" maxLength: ");
+            sb.append(columnOriginal.getMaxLength());
+            sb.append(" -> ");
+            sb.append(columnNew.getMaxLength());
+        }
+
+        if (columnOriginal.getAttributePrecision() != columnNew.getAttributePrecision()) {
+            sb.append(" precision: ");
+            sb.append(columnOriginal.getAttributePrecision());
+            sb.append(" -> ");
+            sb.append(columnNew.getAttributePrecision());
+        }
+
+        if (columnOriginal.getScale() != columnNew.getScale()) {
+            sb.append(" scale: ");
+            sb.append(columnOriginal.getScale());
+            sb.append(" -> ");
+            sb.append(columnNew.getScale());
+        }
+
+        return sb.toString();
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetColumnTypeToModel(getEntity(), columnNew, columnOriginal);
+    }
+
+    public DbAttribute getColumnOriginal() {
+        return columnOriginal;
+    }
+
+    public DbAttribute getColumnNew() {
+        return columnNew;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetColumnTypeToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetColumnTypeToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetColumnTypeToModel.java
new file mode 100644
index 0000000..262c3a6
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetColumnTypeToModel.java
@@ -0,0 +1,102 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * A {@link MergerToken} that modifies one original {@link DbAttribute} to match another
+ * new {@link DbAttribute}s type, maxLength and precision. The name and mandatory fields
+ * are not modified by this token.
+ * 
+ */
+public class SetColumnTypeToModel extends AbstractToModelToken.Entity {
+
+    private DbAttribute columnOriginal;
+    private DbAttribute columnNew;
+
+    public SetColumnTypeToModel(DbEntity entity, DbAttribute columnOriginal,
+            DbAttribute columnNew) {
+        super("Set Column Type", entity);
+        this.columnOriginal = columnOriginal;
+        this.columnNew = columnNew;
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetColumnTypeToDb(getEntity(), columnNew, columnOriginal);
+    }
+
+    public void execute(MergerContext mergerContext) {
+        columnOriginal.setType(columnNew.getType());
+        columnOriginal.setMaxLength(columnNew.getMaxLength());
+        columnOriginal.setAttributePrecision(columnNew.getAttributePrecision());
+        columnOriginal.setScale(columnNew.getScale());
+        mergerContext.getModelMergeDelegate().dbAttributeModified(columnOriginal);
+    }
+
+    @Override
+    public String getTokenValue() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getEntity().getName());
+        sb.append(".");
+        sb.append(columnNew.getName());
+
+        if (columnOriginal.getType() != columnNew.getType()) {
+            sb.append(" type: ");
+            sb.append(TypesMapping.getSqlNameByType(columnOriginal.getType()));
+            sb.append(" -> ");
+            sb.append(TypesMapping.getSqlNameByType(columnNew.getType()));
+        }
+
+        if (columnOriginal.getMaxLength() != columnNew.getMaxLength()) {
+            sb.append(" maxLength: ");
+            sb.append(columnOriginal.getMaxLength());
+            sb.append(" -> ");
+            sb.append(columnNew.getMaxLength());
+        }
+
+        if (columnOriginal.getAttributePrecision() != columnNew.getAttributePrecision()) {
+            sb.append(" precision: ");
+            sb.append(columnOriginal.getAttributePrecision());
+            sb.append(" -> ");
+            sb.append(columnNew.getAttributePrecision());
+        }
+
+        if (columnOriginal.getScale() != columnNew.getScale()) {
+            sb.append(" scale: ");
+            sb.append(columnOriginal.getScale());
+            sb.append(" -> ");
+            sb.append(columnNew.getScale());
+        }
+
+        return sb.toString();
+    }
+    
+    public DbAttribute getColumnOriginal() {
+        return columnOriginal;
+    }
+
+    public DbAttribute getColumnNew() {
+        return columnNew;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetNotNullToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetNotNullToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetNotNullToDb.java
new file mode 100644
index 0000000..169ea34
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetNotNullToDb.java
@@ -0,0 +1,51 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * A {@link MergerToken} to add a "not null" clause to a column.
+ */
+public class SetNotNullToDb extends AbstractToDbToken.EntityAndColumn {
+
+    public SetNotNullToDb(DbEntity entity, DbAttribute column) {
+        super("Set Not Null", entity, column);
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        QuotingStrategy context = adapter.getQuotingStrategy();
+
+        return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
+                + " ALTER COLUMN " + context.quotedName(getColumn()) + " SET NOT NULL");
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetAllowNullToModel(getEntity(), getColumn());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetNotNullToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetNotNullToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetNotNullToModel.java
new file mode 100644
index 0000000..9ae95b5
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetNotNullToModel.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * A {@link MergerToken} to set the mandatory field of a {@link DbAttribute} to true
+ * 
+ */
+public class SetNotNullToModel extends AbstractToModelToken.EntityAndColumn {
+
+    public SetNotNullToModel(DbEntity entity, DbAttribute column) {
+        super("Set Not Null", entity, column);
+    }
+    
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetAllowNullToDb(getEntity(), getColumn());
+    }
+
+    public void execute(MergerContext mergerContext) {
+        getColumn().setMandatory(true);
+        mergerContext.getModelMergeDelegate().dbAttributeModified(getColumn());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDb.java
new file mode 100644
index 0000000..8161b87
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDb.java
@@ -0,0 +1,86 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+public class SetPrimaryKeyToDb extends AbstractToDbToken.Entity {
+
+    private Collection<DbAttribute> primaryKeyOriginal;
+    private Collection<DbAttribute> primaryKeyNew;
+    private String detectedPrimaryKeyName;
+
+    public SetPrimaryKeyToDb(DbEntity entity, Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
+        super("Set Primary Key", entity);
+
+        this.primaryKeyOriginal = primaryKeyOriginal;
+        this.primaryKeyNew = primaryKeyNew;
+        this.detectedPrimaryKeyName = detectedPrimaryKeyName;
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        List<String> sqls = new ArrayList<String>();
+        if (!primaryKeyOriginal.isEmpty()) {
+            appendDropOriginalPrimaryKeySQL(adapter, sqls);
+        }
+        appendAddNewPrimaryKeySQL(adapter, sqls);
+        return sqls;
+    }
+
+    protected void appendDropOriginalPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
+        if (detectedPrimaryKeyName == null) {
+            return;
+        }
+        sqls.add("ALTER TABLE " + adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity())
+                + " DROP CONSTRAINT " + detectedPrimaryKeyName);
+    }
+
+    protected void appendAddNewPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
+        QuotingStrategy quotingStrategy = adapter.getQuotingStrategy();
+
+        StringBuilder sql = new StringBuilder();
+        sql.append("ALTER TABLE ");
+        sql.append(quotingStrategy.quotedFullyQualifiedName(getEntity()));
+        sql.append(" ADD PRIMARY KEY (");
+        for (Iterator<DbAttribute> it = primaryKeyNew.iterator(); it.hasNext();) {
+            sql.append(quotingStrategy.quotedName(it.next()));
+            if (it.hasNext()) {
+                sql.append(", ");
+            }
+        }
+        sql.append(")");
+        sqls.add(sql.toString());
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetPrimaryKeyToModel(getEntity(), primaryKeyNew, primaryKeyOriginal,
+                detectedPrimaryKeyName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToModel.java
new file mode 100644
index 0000000..6785a2f
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToModel.java
@@ -0,0 +1,78 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.event.AttributeEvent;
+
+public class SetPrimaryKeyToModel extends AbstractToModelToken.Entity {
+
+    private Collection<DbAttribute> primaryKeyOriginal;
+    private Collection<DbAttribute> primaryKeyNew;
+    private String detectedPrimaryKeyName;
+    private Set<String> primaryKeyNewAttributeNames = new HashSet<String>();
+
+    public SetPrimaryKeyToModel(DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
+        super("Set Primary Key", entity);
+        
+        this.primaryKeyOriginal = primaryKeyOriginal;
+        this.primaryKeyNew = primaryKeyNew;
+        this.detectedPrimaryKeyName = detectedPrimaryKeyName;
+        
+        for (DbAttribute attr : primaryKeyNew) {
+            primaryKeyNewAttributeNames.add(attr.getName().toUpperCase());
+        }
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetPrimaryKeyToDb(
+                getEntity(),
+                primaryKeyNew,
+                primaryKeyOriginal,
+                detectedPrimaryKeyName);
+    }
+
+    public void execute(MergerContext mergerContext) {
+        DbEntity e = getEntity();
+
+        for (DbAttribute attr : e.getAttributes()) {
+
+            boolean wasPrimaryKey = attr.isPrimaryKey();
+            boolean willBePrimaryKey = primaryKeyNewAttributeNames.contains(attr
+                    .getName()
+                    .toUpperCase());
+
+            if (wasPrimaryKey != willBePrimaryKey) {
+                attr.setPrimaryKey(willBePrimaryKey);
+                e.dbAttributeChanged(new AttributeEvent(this, attr, e));
+                mergerContext.getModelMergeDelegate().dbAttributeModified(attr);
+            }
+
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetValueForNullToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetValueForNullToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetValueForNullToDb.java
new file mode 100644
index 0000000..1ffdd49
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/SetValueForNullToDb.java
@@ -0,0 +1,47 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.List;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+
+public class SetValueForNullToDb extends AbstractToDbToken.EntityAndColumn {
+    
+    private ValueForNullProvider valueForNullProvider;
+
+    public SetValueForNullToDb(DbEntity entity, DbAttribute column, ValueForNullProvider valueForNullProvider) {
+        super("Set value for null", entity, column);
+        this.valueForNullProvider = valueForNullProvider;
+    }
+    
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        return valueForNullProvider.createSql(getEntity(), getColumn());
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return new DummyReverseToken(this);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ValueForNullProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ValueForNullProvider.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ValueForNullProvider.java
new file mode 100644
index 0000000..a07efff
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/ValueForNullProvider.java
@@ -0,0 +1,42 @@
+/*****************************************************************
+ *   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.dbsync.merge;
+
+import java.util.List;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+/**
+ * Class that will be used to set value for null on not
+ * null columns
+ */
+public interface ValueForNullProvider {
+    
+    /**
+     * @return true if there exist a value that should be inserted for null values
+     */
+    public boolean hasValueFor(DbEntity entity, DbAttribute column);
+
+    /**
+     * @return a {@link List} of sql to set value for null
+     */
+    public List<String> createSql(DbEntity entity, DbAttribute column);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DB2MergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DB2MergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DB2MergerTokenFactory.java
new file mode 100644
index 0000000..586a5cd
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DB2MergerTokenFactory.java
@@ -0,0 +1,47 @@
+/*****************************************************************
+ *   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.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+public class DB2MergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER COLUMN ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" SET DATA TYPE ");
+            }
+        };
+    }
+}