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/12/14 19:12:38 UTC

[02/16] cayenne git commit: New DbMerger for dbsync utils - merger process split in independent steps - new attributes compared (full type comparision of DbAttribute)

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/CreateTableToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/CreateTableToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/CreateTableToDb.java
new file mode 100644
index 0000000..6774b89
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/CreateTableToDb.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.token;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+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>();
+        if(needAutoPkSupport()) {
+            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();
+            if(needAutoPkSupport()) {
+                adapter.getPkGenerator().createAutoPk(
+                        node,
+                        Collections.singletonList(getEntity()));
+            }
+            executeSql(mergerContext, adapter.createTable(getEntity()));
+        }
+        catch (Exception e) {
+            mergerContext.getValidationResult().addFailure(
+                    new SimpleValidationFailure(this, e.getMessage()));
+        }
+    }
+
+    private boolean needAutoPkSupport() {
+        DbEntity entity = getEntity();
+        if(entity.getPrimaryKeyGenerator() != null) {
+            return false;
+        }
+
+        for(DbAttribute attribute : entity.getPrimaryKeys()) {
+            if(attribute.isGenerated()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createDropTableToModel(getEntity());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/CreateTableToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/CreateTableToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/CreateTableToModel.java
new file mode 100644
index 0000000..38f710f
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/CreateTableToModel.java
@@ -0,0 +1,100 @@
+/*****************************************************************
+ *   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.token;
+
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.dbsync.naming.NameBuilder;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjEntity;
+
+/**
+ * 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;
+
+    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;
+    }
+
+    @Override
+    public void execute(MergerContext context) {
+        DbEntity dbEntity = getEntity();
+
+        DataMap map = context.getDataMap();
+        map.addDbEntity(dbEntity);
+
+        // create a ObjEntity
+        ObjEntity objEntity = new ObjEntity();
+
+        objEntity.setName(NameBuilder
+                .builder(objEntity, dbEntity.getDataMap())
+                .baseName(context.getNameGenerator().objEntityName(dbEntity))
+                .name());
+        objEntity.setDbEntity(getEntity());
+
+        // try to find a class name for the ObjEntity
+        String className = objEntityClassName;
+        if (className == null) {
+            // generate a className based on the objEntityName
+            className = map.getNameWithDefaultPackage(objEntity.getName());
+        }
+
+        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...
+        context.getEntityMergeSupport().synchronizeWithDbEntity(objEntity);
+
+        context.getDelegate().dbEntityAdded(getEntity());
+        context.getDelegate().objEntityAdded(objEntity);
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createDropTableToDb(getEntity());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DefaultValueForNullProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DefaultValueForNullProvider.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DefaultValueForNullProvider.java
new file mode 100644
index 0000000..54fd030
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DefaultValueForNullProvider.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.token;
+
+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/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropColumnToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropColumnToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropColumnToDb.java
new file mode 100644
index 0000000..ecdb970
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropColumnToDb.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.token;
+
+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());
+    }
+
+    @Override
+    public int compareTo(MergerToken o) {
+        // add all AddRelationshipToDb to the end.
+        if (o instanceof DropRelationshipToDb) {
+            return 1;
+        }
+        return super.compareTo(o);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropColumnToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropColumnToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropColumnToModel.java
new file mode 100644
index 0000000..6c9b09c
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropColumnToModel.java
@@ -0,0 +1,79 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dbsync.merge.token;
+
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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);
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createAddColumnToDb(getEntity(), getColumn());
+    }
+
+    @Override
+    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.getDelegate(), 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.getDelegate().objAttributeRemoved(objAttribute);
+            }
+
+        }
+
+        // remove DbAttribute
+        getEntity().removeAttribute(getColumn().getName());
+
+        mergerContext.getDelegate().dbAttributeRemoved(getColumn());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropRelationshipToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropRelationshipToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropRelationshipToDb.java
new file mode 100644
index 0000000..dce2b81
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropRelationshipToDb.java
@@ -0,0 +1,84 @@
+/*****************************************************************
+ *   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.token;
+
+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.dbsync.reverse.db.DbRelationshipDetected;
+
+import java.util.Collections;
+import java.util.List;
+
+public class DropRelationshipToDb extends AbstractToDbToken.Entity {
+
+    private DbRelationship relationship;
+
+    public DropRelationshipToDb(DbEntity entity, DbRelationship relationship) {
+        super("Drop foreign key", entity);
+        this.relationship = relationship;
+    }
+    
+    public String getFkName() {
+        if (relationship instanceof DbRelationshipDetected) {
+            return ((DbRelationshipDetected) relationship).getFkName();
+        }
+        return null;
+    }
+
+    @Override
+    public List<String> createSql(DbAdapter adapter) {
+        if (isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        QuotingStrategy context = adapter.getQuotingStrategy();
+        return Collections.singletonList(
+                "ALTER TABLE " + context.quotedFullyQualifiedName(getEntity()) + " DROP CONSTRAINT " + getFkName());
+    }
+
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createAddRelationshipToModel(getEntity(), relationship);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return getFkName() == null || relationship.isToMany();
+    }
+
+    @Override
+    public String getTokenValue() {
+        if(relationship.isToMany()) {
+            return "Skip. No sql representation.";
+        }
+        return relationship.getSourceEntity().getName() + "->" + relationship.getTargetEntityName();
+    }
+
+    @Override
+    public int compareTo(MergerToken o) {
+        // add all AddRelationshipToDb to the end.
+        if (o instanceof DropRelationshipToDb) {
+            return super.compareTo(o);
+        }
+        return -1;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropRelationshipToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropRelationshipToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropRelationshipToModel.java
new file mode 100644
index 0000000..d30355d
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/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.token;
+
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+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 relationship;
+
+    public DropRelationshipToModel(DbEntity entity, DbRelationship relationship) {
+        super("Drop db-relationship ", entity);
+        this.relationship = relationship;
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createAddRelationshipToDb(getEntity(), relationship);
+    }
+
+    @Override
+    public void execute(MergerContext mergerContext) {
+        remove(mergerContext.getDelegate(), relationship, true);
+    }
+
+    @Override
+    public String getTokenValue() {
+        return AddRelationshipToModel.getTokenValue(relationship);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropTableToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropTableToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropTableToDb.java
new file mode 100644
index 0000000..d5dd161
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropTableToDb.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.token;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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<>();
+        // 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;
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createCreateTableToModel(getEntity());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropTableToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropTableToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropTableToModel.java
new file mode 100644
index 0000000..1773ec6
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DropTableToModel.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.token;
+
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+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);
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createCreateTableToDb(getEntity());
+    }
+
+    @Override
+    public void execute(MergerContext mergerContext) {
+        for (ObjEntity objEntity : getEntity().mappedObjEntities()) {
+            objEntity.getDataMap().removeObjEntity(objEntity.getName(), true);
+            mergerContext.getDelegate().objEntityRemoved(objEntity);
+        }
+        getEntity().getDataMap().removeDbEntity(getEntity().getName(), true);
+        mergerContext.getDelegate().dbEntityRemoved(getEntity());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DummyReverseToken.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DummyReverseToken.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DummyReverseToken.java
new file mode 100644
index 0000000..6bdb4d9
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/DummyReverseToken.java
@@ -0,0 +1,68 @@
+/*****************************************************************
+ *   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.token;
+
+import org.apache.cayenne.dbsync.merge.context.MergeDirection;
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+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;
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return reverse;
+    }
+
+    @Override
+    public void execute(MergerContext mergerContext) {
+        // can not execute
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return true;
+    }
+
+    @Override
+    public MergeDirection getDirection() {
+        return reverse.getDirection().reverseDirection();
+    }
+
+    @Override
+    public String getTokenName() {
+        return "Can not execute the reverse of " + reverse.getTokenName();
+    }
+
+    @Override
+    public String getTokenValue() {
+        return reverse.getTokenValue();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/EmptyValueForNullProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/EmptyValueForNullProvider.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/EmptyValueForNullProvider.java
new file mode 100644
index 0000000..e468642
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/EmptyValueForNullProvider.java
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   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.token;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A dummy {@link ValueForNullProvider} that are not able to provide any values.
+ */
+public 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/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/MergerToken.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/MergerToken.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/MergerToken.java
new file mode 100644
index 0000000..954f512
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/MergerToken.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.token;
+
+import org.apache.cayenne.dbsync.merge.context.MergeDirection;
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+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 context merge operation context.
+     */
+    void execute(MergerContext context);
+
+    boolean isEmpty();
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetAllowNullToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetAllowNullToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetAllowNullToDb.java
new file mode 100644
index 0000000..f155680
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetAllowNullToDb.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.merge.token;
+
+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;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 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());
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetNotNullToModel(getEntity(), getColumn());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetAllowNullToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetAllowNullToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetAllowNullToModel.java
new file mode 100644
index 0000000..b9e7f19
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetAllowNullToModel.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.token;
+
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+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);
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetNotNullToDb(getEntity(), getColumn());
+    }
+
+    @Override
+    public void execute(MergerContext mergerContext) {
+        getColumn().setMandatory(false);
+        mergerContext.getDelegate().dbAttributeModified(getColumn());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetColumnTypeToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetColumnTypeToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetColumnTypeToDb.java
new file mode 100644
index 0000000..d8eeca9
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetColumnTypeToDb.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.token;
+
+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;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 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();
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetColumnTypeToModel(getEntity(), columnNew, columnOriginal);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetColumnTypeToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetColumnTypeToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetColumnTypeToModel.java
new file mode 100644
index 0000000..26730a9
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetColumnTypeToModel.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.token;
+
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+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;
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetColumnTypeToDb(getEntity(), columnNew, columnOriginal);
+    }
+
+    @Override
+    public void execute(MergerContext mergerContext) {
+        columnOriginal.setType(columnNew.getType());
+        columnOriginal.setMaxLength(columnNew.getMaxLength());
+        columnOriginal.setAttributePrecision(columnNew.getAttributePrecision());
+        columnOriginal.setScale(columnNew.getScale());
+        mergerContext.getDelegate().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();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetNotNullToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetNotNullToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetNotNullToDb.java
new file mode 100644
index 0000000..a0fe583
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetNotNullToDb.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.token;
+
+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;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 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");
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetAllowNullToModel(getEntity(), getColumn());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetNotNullToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetNotNullToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetNotNullToModel.java
new file mode 100644
index 0000000..50a4b43
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetNotNullToModel.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.token;
+
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+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);
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetAllowNullToDb(getEntity(), getColumn());
+    }
+
+    @Override
+    public void execute(MergerContext mergerContext) {
+        getColumn().setMandatory(true);
+        mergerContext.getDelegate().dbAttributeModified(getColumn());
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetPrimaryKeyToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetPrimaryKeyToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetPrimaryKeyToDb.java
new file mode 100644
index 0000000..6380555
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetPrimaryKeyToDb.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.token;
+
+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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+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());
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetPrimaryKeyToModel(getEntity(), primaryKeyNew, primaryKeyOriginal,
+                detectedPrimaryKeyName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetPrimaryKeyToModel.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetPrimaryKeyToModel.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetPrimaryKeyToModel.java
new file mode 100644
index 0000000..86e0ac3
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetPrimaryKeyToModel.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.token;
+
+import org.apache.cayenne.dbsync.merge.context.MergerContext;
+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;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+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());
+        }
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return factory.createSetPrimaryKeyToDb(
+                getEntity(),
+                primaryKeyNew,
+                primaryKeyOriginal,
+                detectedPrimaryKeyName);
+    }
+
+    @Override
+    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.getDelegate().dbAttributeModified(attr);
+            }
+
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetValueForNullToDb.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetValueForNullToDb.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetValueForNullToDb.java
new file mode 100644
index 0000000..b83d619
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/SetValueForNullToDb.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.token;
+
+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;
+
+import java.util.List;
+
+
+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());
+    }
+
+    @Override
+    public MergerToken createReverse(MergerTokenFactory factory) {
+        return new DummyReverseToken(this);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/TokenComparator.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/TokenComparator.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/TokenComparator.java
new file mode 100644
index 0000000..4d9754a
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/TokenComparator.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.dbsync.merge.token;
+
+import java.util.Comparator;
+
+/**
+ * Simple sort of merge tokens.
+ * Just move all relationships creation tokens to the end of the list.
+ */
+public class TokenComparator implements Comparator<MergerToken> {
+
+    @Override
+    public int compare(MergerToken o1, MergerToken o2) {
+        if (o1 instanceof AbstractToDbToken && o2 instanceof AbstractToDbToken) {
+            return ((AbstractToDbToken) o1).compareTo(o2);
+        }
+
+        if (o1 instanceof AddRelationshipToModel && o2 instanceof AddRelationshipToModel) {
+            return 0;
+        }
+
+        if (!(o1 instanceof AddRelationshipToModel || o2 instanceof AddRelationshipToModel)) {
+            return o1.getClass().getSimpleName().compareTo(o2.getClass().getSimpleName());
+        }
+
+        return o1 instanceof AddRelationshipToModel ? 1 : -1;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/ValueForNullProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/ValueForNullProvider.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/ValueForNullProvider.java
new file mode 100644
index 0000000..7bdcd7b
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/token/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.token;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.List;
+
+/**
+ * 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
+     */
+    boolean hasValueFor(DbEntity entity, DbAttribute column);
+
+    /**
+     * @return a {@link List} of sql to set value for null
+     */
+    List<String> createSql(DbEntity entity, DbAttribute column);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/DefaultModelMergeDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/DefaultModelMergeDelegate.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/DefaultModelMergeDelegate.java
new file mode 100644
index 0000000..228fbbe
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/DefaultModelMergeDelegate.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.reverse.dbload;
+
+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/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ModelMergeDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ModelMergeDelegate.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ModelMergeDelegate.java
new file mode 100644
index 0000000..9e28f1a
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ModelMergeDelegate.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.reverse.dbload;
+
+import org.apache.cayenne.dbsync.merge.context.MergeDirection;
+import org.apache.cayenne.dbsync.merge.token.MergerToken;
+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 {
+
+    void dbEntityAdded(DbEntity ent);
+
+    void dbEntityRemoved(DbEntity ent);
+
+    void objEntityAdded(ObjEntity ent);
+
+    void objEntityRemoved(ObjEntity ent);
+
+    void dbAttributeAdded(DbAttribute att);
+
+    void dbAttributeRemoved(DbAttribute att);
+
+    void dbAttributeModified(DbAttribute att);
+
+    void objAttributeAdded(ObjAttribute att);
+
+    void objAttributeRemoved(ObjAttribute att);
+
+    void objAttributeModified(ObjAttribute att);
+
+    void dbRelationshipAdded(DbRelationship rel);
+
+    void dbRelationshipRemoved(DbRelationship rel);
+
+    void objRelationshipAdded(ObjRelationship rel);
+
+    void objRelationshipRemoved(ObjRelationship rel);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ProxyModelMergeDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ProxyModelMergeDelegate.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ProxyModelMergeDelegate.java
new file mode 100644
index 0000000..7793bbd
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/dbload/ProxyModelMergeDelegate.java
@@ -0,0 +1,109 @@
+/*****************************************************************
+ *   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.dbload;
+
+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/46c8ded5/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
index 30fa47b..318e0da 100644
--- 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
@@ -26,6 +26,8 @@ import static org.junit.Assert.assertTrue;
 import java.sql.Types;
 import java.util.List;
 
+import org.apache.cayenne.dbsync.merge.token.AddColumnToModel;
+import org.apache.cayenne.dbsync.merge.token.MergerToken;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.ObjAttribute;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/46c8ded5/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
index bac6402..b3a695b 100644
--- 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
@@ -16,6 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  ****************************************************************/
+
 package org.apache.cayenne.dbsync.merge;
 
 import static org.junit.Assert.assertEquals;
@@ -27,6 +28,8 @@ import static org.junit.Assert.assertTrue;
 import java.sql.Types;
 import java.util.List;
 
+import org.apache.cayenne.dbsync.merge.token.CreateTableToModel;
+import org.apache.cayenne.dbsync.merge.token.MergerToken;
 import org.apache.cayenne.map.DbAttribute;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.ObjEntity;