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:39:01 UTC

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

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());
+    }
+
+}