You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2016/09/29 17:38:59 UTC

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

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DefaultMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DefaultMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DefaultMergerTokenFactory.java
new file mode 100644
index 0000000..c2f96ce
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DefaultMergerTokenFactory.java
@@ -0,0 +1,181 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.AddColumnToModel;
+import org.apache.cayenne.dbsync.merge.AddRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.AddRelationshipToModel;
+import org.apache.cayenne.dbsync.merge.CreateTableToDb;
+import org.apache.cayenne.dbsync.merge.CreateTableToModel;
+import org.apache.cayenne.dbsync.merge.DropColumnToDb;
+import org.apache.cayenne.dbsync.merge.DropColumnToModel;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToModel;
+import org.apache.cayenne.dbsync.merge.DropTableToDb;
+import org.apache.cayenne.dbsync.merge.DropTableToModel;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToModel;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToModel;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToModel;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToDb;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToModel;
+import org.apache.cayenne.dbsync.merge.SetValueForNullToDb;
+import org.apache.cayenne.dbsync.merge.ValueForNullProvider;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.Collection;
+
+/**
+ * @since 4.0
+ */
+public class DefaultMergerTokenFactory implements MergerTokenFactory {
+
+    @Override
+    public MergerToken createCreateTableToModel(DbEntity entity) {
+        return new CreateTableToModel(entity);
+    }
+
+    @Override
+    public MergerToken createCreateTableToDb(DbEntity entity) {
+        return new CreateTableToDb(entity);
+    }
+
+    @Override
+    public MergerToken createDropTableToModel(DbEntity entity) {
+        return new DropTableToModel(entity);
+    }
+
+    @Override
+    public MergerToken createDropTableToDb(DbEntity entity) {
+        return new DropTableToDb(entity);
+    }
+
+    @Override
+    public MergerToken createAddColumnToModel(DbEntity entity, DbAttribute column) {
+        return new AddColumnToModel(entity, column);
+    }
+
+    @Override
+    public MergerToken createAddColumnToDb(DbEntity entity, DbAttribute column) {
+        return new AddColumnToDb(entity, column);
+    }
+
+    @Override
+    public MergerToken createDropColumnToModel(DbEntity entity, DbAttribute column) {
+        return new DropColumnToModel(entity, column);
+    }
+
+    @Override
+    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
+        return new DropColumnToDb(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetNotNullToModel(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToModel(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToModel(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToModel(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column);
+    }
+
+    @Override
+    public MergerToken createSetValueForNullToDb(DbEntity entity, DbAttribute column, ValueForNullProvider valueForNullProvider) {
+        return new SetValueForNullToDb(entity, column, valueForNullProvider);
+    }
+
+    @Override
+    public MergerToken createSetColumnTypeToModel(
+            DbEntity entity,
+            DbAttribute columnOriginal,
+            DbAttribute columnNew) {
+        return new SetColumnTypeToModel(entity, columnOriginal, columnNew);
+    }
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            DbEntity entity,
+            DbAttribute columnOriginal,
+            DbAttribute columnNew) {
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew);
+    }
+
+    @Override
+    public MergerToken createAddRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        return new AddRelationshipToDb(entity, rel);
+    }
+
+    @Override
+    public MergerToken createAddRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        return new AddRelationshipToModel(entity, rel);
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(DbEntity entity, DbRelationship rel) {
+        return new DropRelationshipToDb(entity, rel);
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToModel(DbEntity entity, DbRelationship rel) {
+        return new DropRelationshipToModel(entity, rel);
+    }
+
+    @Override
+    public MergerToken createSetPrimaryKeyToDb(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToDb(
+                entity,
+                primaryKeyOriginal,
+                primaryKeyNew,
+                detectedPrimaryKeyName);
+    }
+
+    @Override
+    public MergerToken createSetPrimaryKeyToModel(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToModel(
+                entity,
+                primaryKeyOriginal,
+                primaryKeyNew,
+                detectedPrimaryKeyName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DerbyMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DerbyMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DerbyMergerTokenFactory.java
new file mode 100644
index 0000000..398d5cc
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/DerbyMergerTokenFactory.java
@@ -0,0 +1,85 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+public class DerbyMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://db.apache.org/derby/manuals/reference/sqlj26.html
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" SET DATA TYPE ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
+                        + " ALTER COLUMN " + context.quotedName(getColumn()) + " NOT NULL");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
+                        + " ALTER COLUMN " + context.quotedName(getColumn()) + " NULL");
+            }
+
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/FirebirdMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/FirebirdMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/FirebirdMergerTokenFactory.java
new file mode 100644
index 0000000..4368977
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/FirebirdMergerTokenFactory.java
@@ -0,0 +1,91 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+ 
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.DropColumnToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+public class FirebirdMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
+        return new DropColumnToDb(entity, column) {
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy quoting = adapter.getQuotingStrategy();
+                return Collections.singletonList("ALTER TABLE " + quoting.quotedFullyQualifiedName(getEntity())
+                        + " DROP " + quoting.quotedName(getColumn()));
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                String entityName = context.quotedFullyQualifiedName(getEntity()) ;
+                String columnName = context.quotedName(getColumn());
+                // Firebird doesn't support ALTER TABLE table_name ALTER column_name SET NOT NULL
+                // but this might be achived by modyfication of system tables 
+                return Collections.singletonList(String.format("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = 1 "+ 
+                "WHERE RDB$FIELD_NAME = '%s' AND RDB$RELATION_NAME = '%s'", columnName, entityName));
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                String entityName = context.quotedFullyQualifiedName(getEntity()) ;
+                String columnName = context.quotedName(getColumn()); 
+                // Firebird doesn't support ALTER TABLE table_name ALTER column_name DROP NOT NULL
+                // but this might be achived by modyfication system tables 
+                return Collections.singletonList(String.format("UPDATE RDB$RELATION_FIELDS SET RDB$NULL_FLAG = NULL "+
+                " WHERE RDB$FIELD_NAME = '%s' AND RDB$RELATION_NAME = '%s'", columnName, entityName));
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createAddColumnToDb(DbEntity entity, DbAttribute column) {
+        return new AddColumnToDb(entity, column) {
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ADD ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/H2MergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/H2MergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/H2MergerTokenFactory.java
new file mode 100644
index 0000000..8acafc3
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/H2MergerTokenFactory.java
@@ -0,0 +1,82 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @since 3.0
+ */
+public class H2MergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                return Collections.singletonList("ALTER TABLE " + getEntity().getFullyQualifiedName()
+                        + " ALTER COLUMN " + getColumn().getName() + " SET NULL");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetPrimaryKeyToDb(DbEntity entity, Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToDb(entity, primaryKeyOriginal, primaryKeyNew, detectedPrimaryKeyName) {
+
+            @Override
+            protected void appendDropOriginalPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
+                sqls.add("ALTER TABLE " + adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity())
+                        + " DROP PRIMARY KEY");
+            }
+
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/HSQLMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/HSQLMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/HSQLMergerTokenFactory.java
new file mode 100644
index 0000000..15cfa18
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/HSQLMergerTokenFactory.java
@@ -0,0 +1,82 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class HSQLMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(getEntity())
+                        + " ALTER COLUMN " + context.quotedName(getColumn()) + " NULL");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetPrimaryKeyToDb(DbEntity entity, Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew, String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToDb(entity, primaryKeyOriginal, primaryKeyNew, detectedPrimaryKeyName) {
+
+            @Override
+            protected void appendDropOriginalPrimaryKeySQL(DbAdapter adapter, List<String> sqls) {
+                sqls.add("ALTER TABLE " + adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity())
+                        + " DROP PRIMARY KEY");
+            }
+
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
new file mode 100644
index 0000000..19d2860
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/IngresMergerTokenFactory.java
@@ -0,0 +1,224 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.DropColumnToDb;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.Collections;
+import java.util.List;
+
+public class IngresMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(final DbEntity entity, DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER COLUMN ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
+        return new DropColumnToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuilder buf = new StringBuilder();
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                buf.append("ALTER TABLE ");
+                buf.append(context.quotedFullyQualifiedName(getEntity()));
+                buf.append(" DROP COLUMN ");
+                buf.append(context.quotedName(getColumn()));
+                buf.append(" RESTRICT ");
+
+                return Collections.singletonList(buf.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createAddRelationshipToDb(DbEntity entity, final DbRelationship rel) {
+        return new AddRelationshipToDb(entity, rel) {
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                if (!rel.isToMany() && rel.isToPK() && !rel.isToDependentPK()) {
+
+                    DbEntity source = (DbEntity) rel.getSourceEntity();
+                    QuotingStrategy context = adapter.getQuotingStrategy();
+                    StringBuilder buf = new StringBuilder();
+                    StringBuilder refBuf = new StringBuilder();
+
+                    buf.append("ALTER TABLE ");
+                    buf.append(context.quotedFullyQualifiedName(source));
+
+                    // requires the ADD CONSTRAINT statement
+                    buf.append(" ADD CONSTRAINT ");
+                    String name = "U_" + rel.getSourceEntity().getName() + "_"
+                            + (long) (System.currentTimeMillis() / (Math.random() * 100000));
+
+                    buf.append(context.quotedIdentifier(rel.getSourceEntity(), name));
+                    buf.append(" FOREIGN KEY (");
+
+                    boolean first = true;
+                    for (DbJoin join : rel.getJoins()) {
+                        if (!first) {
+                            buf.append(", ");
+                            refBuf.append(", ");
+                        } else
+                            first = false;
+
+                        buf.append(context.quotedSourceName(join));
+                        refBuf.append(context.quotedTargetName(join));
+                    }
+
+                    buf.append(") REFERENCES ");
+                    buf.append(context.quotedFullyQualifiedName((DbEntity) rel.getTargetEntity()));
+                    buf.append(" (");
+                    buf.append(refBuf.toString());
+                    buf.append(')');
+
+                    // also make sure we delete dependent FKs
+                    buf.append(" ON DELETE CASCADE");
+
+                    String fksql = buf.toString();
+
+                    if (fksql != null) {
+                        return Collections.singletonList(fksql);
+                    }
+                }
+
+                return Collections.emptyList();
+
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                /*
+                 * TODO: we generate this query as in ingres db documentation,
+                 * but unfortunately ingres don't support it
+                 */
+
+                StringBuilder sqlBuffer = new StringBuilder();
+
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(getEntity().getFullyQualifiedName());
+                sqlBuffer.append(" ALTER COLUMN ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+                sqlBuffer.append(adapter.externalTypesForJdbcType(getColumn().getType())[0]);
+
+                if (adapter.typeSupportsLength(getColumn().getType()) && getColumn().getMaxLength() > 0) {
+                    sqlBuffer.append("(");
+                    sqlBuffer.append(getColumn().getMaxLength());
+                    sqlBuffer.append(")");
+                }
+
+                sqlBuffer.append(" NOT NULL");
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuilder sqlBuffer = new StringBuilder();
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ALTER COLUMN ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+                sqlBuffer.append(adapter.externalTypesForJdbcType(getColumn().getType())[0]);
+
+                if (adapter.typeSupportsLength(getColumn().getType()) && getColumn().getMaxLength() > 0) {
+                    sqlBuffer.append("(");
+                    sqlBuffer.append(getColumn().getMaxLength());
+                    sqlBuffer.append(")");
+                }
+
+                sqlBuffer.append(" WITH NULL");
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(final DbEntity entity, DbRelationship rel) {
+
+        return new DropRelationshipToDb(entity, rel) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                String fkName = getFkName();
+
+                if (fkName == null) {
+                    return Collections.emptyList();
+                }
+                
+                StringBuilder buf = new StringBuilder();
+                buf.append("ALTER TABLE ");
+                buf.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                buf.append(" DROP CONSTRAINT ");
+                buf.append(fkName);
+                buf.append(" CASCADE ");
+
+                return Collections.singletonList(buf.toString());
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactory.java
new file mode 100644
index 0000000..46b6ef3
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactory.java
@@ -0,0 +1,88 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.ValueForNullProvider;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.Collection;
+
+public interface MergerTokenFactory {
+
+    MergerToken createCreateTableToModel(DbEntity entity);
+
+    MergerToken createCreateTableToDb(DbEntity entity);
+
+    MergerToken createDropTableToModel(DbEntity entity);
+
+    MergerToken createDropTableToDb(DbEntity entity);
+
+    MergerToken createAddColumnToModel(DbEntity entity, DbAttribute column);
+
+    MergerToken createAddColumnToDb(DbEntity entity, DbAttribute column);
+
+    MergerToken createDropColumnToModel(DbEntity entity, DbAttribute column);
+
+    MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetNotNullToModel(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetAllowNullToModel(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column);
+
+    MergerToken createSetValueForNullToDb(DbEntity entity,
+                                          DbAttribute column,
+                                          ValueForNullProvider valueForNullProvider);
+
+    MergerToken createSetColumnTypeToModel(
+            DbEntity entity,
+            DbAttribute columnOriginal,
+            DbAttribute columnNew);
+
+    MergerToken createSetColumnTypeToDb(
+            DbEntity entity,
+            DbAttribute columnOriginal,
+            DbAttribute columnNew);
+
+    MergerToken createAddRelationshipToDb(DbEntity entity, DbRelationship rel);
+
+    MergerToken createAddRelationshipToModel(DbEntity entity, DbRelationship rel);
+
+    MergerToken createDropRelationshipToDb(DbEntity entity, DbRelationship rel);
+
+    MergerToken createDropRelationshipToModel(DbEntity entity, DbRelationship rel);
+
+    MergerToken createSetPrimaryKeyToDb(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName);
+
+    MergerToken createSetPrimaryKeyToModel(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactoryProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactoryProvider.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactoryProvider.java
new file mode 100644
index 0000000..8d5b573
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MergerTokenFactoryProvider.java
@@ -0,0 +1,36 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.PerAdapterProvider;
+import org.apache.cayenne.dbsync.CayenneDbSyncModule;
+import org.apache.cayenne.di.Inject;
+
+import java.util.Map;
+
+/**
+ * @since 4.0
+ */
+public class MergerTokenFactoryProvider extends PerAdapterProvider<MergerTokenFactory> {
+
+    public MergerTokenFactoryProvider(@Inject(CayenneDbSyncModule.MERGER_FACTORIES_MAP) Map<String,
+            MergerTokenFactory> perAdapterValues, @Inject MergerTokenFactory defaultValue) {
+        super(perAdapterValues, defaultValue);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MySQLMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MySQLMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MySQLMergerTokenFactory.java
new file mode 100644
index 0000000..2193446
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/MySQLMergerTokenFactory.java
@@ -0,0 +1,156 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.dbsync.merge.SetPrimaryKeyToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class MySQLMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetNotNullToDb(
+            final DbEntity entity,
+            final DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" CHANGE ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(
+            final DbEntity entity,
+            final DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" CHANGE ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+                sqlBuffer.append(" ");
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://dev.mysql.com/tech-resources/articles/mysql-cluster-50.html
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" MODIFY ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(
+            final DbEntity entity,
+            DbRelationship rel) {
+
+        return new DropRelationshipToDb(entity, rel) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                String fkName = getFkName();
+
+                if (fkName == null) {
+                    return Collections.emptyList();
+                }
+                QuotingStrategy context = adapter.getQuotingStrategy();
+
+                // http://dev.mysql.com/tech-resources/articles/mysql-cluster-50.html
+                return Collections.singletonList("ALTER TABLE " + context.quotedFullyQualifiedName(entity) + " DROP FOREIGN KEY " + fkName);
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetPrimaryKeyToDb(
+            DbEntity entity,
+            Collection<DbAttribute> primaryKeyOriginal,
+            Collection<DbAttribute> primaryKeyNew,
+            String detectedPrimaryKeyName) {
+        return new SetPrimaryKeyToDb(
+                entity,
+                primaryKeyOriginal,
+                primaryKeyNew,
+                detectedPrimaryKeyName) {
+
+            @Override
+            protected void appendDropOriginalPrimaryKeySQL(
+                    DbAdapter adapter,
+                    List<String> sqls) {
+                sqls.add("ALTER TABLE "
+                        + adapter.getQuotingStrategy()
+                                .quotedFullyQualifiedName(getEntity())
+                        + " DROP PRIMARY KEY");
+            }
+
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OpenBaseMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OpenBaseMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OpenBaseMergerTokenFactory.java
new file mode 100644
index 0000000..7235f6b
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OpenBaseMergerTokenFactory.java
@@ -0,0 +1,143 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.CreateTableToDb;
+import org.apache.cayenne.dbsync.merge.DropRelationshipToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class OpenBaseMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createCreateTableToDb(DbEntity entity) {
+        return new CreateTableToDb(entity) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                List<String> sqls = new ArrayList<String>();
+                // create table first as OpenBase adapter created primary key in its
+                // getPkGenerator().createAutoPkStatements
+                sqls.add(adapter.createTable(getEntity()));
+                sqls.addAll(adapter.getPkGenerator().createAutoPkStatements(
+                        Collections.singletonList(getEntity())));
+                return sqls;
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createDropRelationshipToDb(
+            final DbEntity entity,
+            final DbRelationship rel) {
+        return new DropRelationshipToDb(entity, rel) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+
+                // FK_NAME form jdbc metadata seem to be wrong. It contain a column name
+                // and not the 'relationshipName'
+                // TODO: tell openbase developer mail list
+
+                DbEntity source = getEntity();
+                DbEntity dest = rel.getTargetEntity();
+
+                // only use the first. See adapter
+                // TODO: can we be sure this is the first and same as used by the adapter?
+                DbJoin join = rel.getJoins().get(0);
+
+                // see comment in adapter for why source and dest is switched around..
+
+                return Collections.singletonList("delete from _SYS_RELATIONSHIP where "
+                        + " source_table = '" + dest.getFullyQualifiedName() + "'"
+                        + " and source_column = '" + join.getTargetName() + "'"
+                        + " and dest_table = '" + source.getFullyQualifiedName() + "'"
+                        + " and dest_column = '" + join.getSourceName() + "'");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            final DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                List<String> sqls = new ArrayList<String>();
+
+                if (columnOriginal.getMaxLength() != columnNew.getMaxLength()) {
+                    sqls.add("ALTER TABLE "
+                            + entity.getFullyQualifiedName()
+                            + " COLUMN "
+                            + columnNew.getName()
+                            + " SET LENGTH "
+                            + columnNew.getMaxLength());
+                }
+
+                return sqls;
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                return Collections.singletonList("ALTER TABLE " + getEntity().getFullyQualifiedName()
+                        + " COLUMN " + getColumn().getName() + " SET NOT NULL");
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                return Collections.singletonList("ALTER TABLE " + getEntity().getFullyQualifiedName()
+                        + " COLUMN " + getColumn().getName() + " SET NULL");
+            }
+
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OracleMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OracleMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OracleMergerTokenFactory.java
new file mode 100644
index 0000000..2c4032b
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/OracleMergerTokenFactory.java
@@ -0,0 +1,111 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+
+public class OracleMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createAddColumnToDb(final DbEntity entity, final DbAttribute column) {
+        return new AddColumnToDb(entity, column) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ADD ");
+                sqlBuffer.append(context.quotedName(column));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" MODIFY ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+    
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, final DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" MODIFY ");
+
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, final DbAttribute column) {
+
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" MODIFY ");
+
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+}

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

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SQLServerMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SQLServerMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SQLServerMergerTokenFactory.java
new file mode 100644
index 0000000..768b957
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SQLServerMergerTokenFactory.java
@@ -0,0 +1,112 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+public class SQLServerMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://msdn2.microsoft.com/en-us/library/ms190273.aspx
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ALTER COLUMN ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createAddColumnToDb(final DbEntity entity, final DbAttribute column) {
+        return new AddColumnToDb(entity, column) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://msdn2.microsoft.com/en-us/library/ms190273.aspx
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" ADD ");
+                sqlBuffer.append(context.quotedName(column));
+                sqlBuffer.append(" ");
+            }
+        };
+    }
+
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, final DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ALTER COLUMN ");
+
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, final DbAttribute column) {
+
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = new StringBuffer();
+
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(adapter.getQuotingStrategy().quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ALTER COLUMN ");
+
+                adapter.createTableAppendColumn(sqlBuffer, column);
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SybaseMergerTokenFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SybaseMergerTokenFactory.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SybaseMergerTokenFactory.java
new file mode 100644
index 0000000..f295305
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/merge/factory/SybaseMergerTokenFactory.java
@@ -0,0 +1,166 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.factory;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.QuotingStrategy;
+import org.apache.cayenne.dbsync.merge.AddColumnToDb;
+import org.apache.cayenne.dbsync.merge.DropColumnToDb;
+import org.apache.cayenne.dbsync.merge.MergerToken;
+import org.apache.cayenne.dbsync.merge.SetAllowNullToDb;
+import org.apache.cayenne.dbsync.merge.SetColumnTypeToDb;
+import org.apache.cayenne.dbsync.merge.SetNotNullToDb;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @since 3.0
+ */
+public class SybaseMergerTokenFactory extends DefaultMergerTokenFactory {
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createAddColumnToDb(DbEntity entity, final DbAttribute column) {
+        return new AddColumnToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                StringBuffer sqlBuffer = new StringBuffer();
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" ADD ");
+                boolean magnatory = column.isMandatory();
+                column.setMandatory(false);
+                adapter.createTableAppendColumn(sqlBuffer, column);
+                if(magnatory){
+                    column.setMandatory(magnatory);
+                }
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createDropColumnToDb(DbEntity entity, DbAttribute column) {
+        return new DropColumnToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuilder sqlBuffer = new StringBuilder();
+                QuotingStrategy context = adapter.getQuotingStrategy();
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(getEntity()));
+                sqlBuffer.append(" DROP ");
+                sqlBuffer.append(context.quotedName(getColumn()));
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createSetNotNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetNotNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+
+                StringBuffer sqlBuffer = createStringQuery(
+                        adapter,
+                        getEntity(),
+                        getColumn());
+
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+
+        };
+    }
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createSetAllowNullToDb(DbEntity entity, DbAttribute column) {
+        return new SetAllowNullToDb(entity, column) {
+
+            @Override
+            public List<String> createSql(DbAdapter adapter) {
+                StringBuffer sqlBuffer = createStringQuery(
+                        adapter,
+                        getEntity(),
+                        getColumn());
+                return Collections.singletonList(sqlBuffer.toString());
+            }
+        };
+    }
+
+    /**
+     * @since 3.0
+     */
+    @Override
+    public MergerToken createSetColumnTypeToDb(
+            final DbEntity entity,
+            DbAttribute columnOriginal,
+            final DbAttribute columnNew) {
+
+        return new SetColumnTypeToDb(entity, columnOriginal, columnNew) {
+
+            @Override
+            protected void appendPrefix(StringBuffer sqlBuffer, QuotingStrategy context) {
+                // http://dev.mysql.com/tech-resources/articles/mysql-cluster-50.html
+                sqlBuffer.append("ALTER TABLE ");
+                sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+                sqlBuffer.append(" MODIFY ");
+                sqlBuffer.append(context.quotedName(columnNew));
+                sqlBuffer.append(" ");
+            }
+
+        };
+    }
+
+    private static StringBuffer createStringQuery(
+            DbAdapter adapter,
+            DbEntity entity,
+            DbAttribute column) {
+        StringBuffer sqlBuffer = new StringBuffer();
+        QuotingStrategy context = adapter.getQuotingStrategy();
+        sqlBuffer.append("ALTER TABLE ");
+        sqlBuffer.append(context.quotedFullyQualifiedName(entity));
+        sqlBuffer.append(" MODIFY ");
+        adapter.createTableAppendColumn(sqlBuffer, column);
+
+        return sqlBuffer;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesBaseLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesBaseLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesBaseLoader.java
new file mode 100644
index 0000000..2bb55c0
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesBaseLoader.java
@@ -0,0 +1,107 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.reverse;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Set;
+
+/**
+* @since 4.0.
+*/
+public abstract class DbAttributesBaseLoader implements DbAttributesLoader {
+    private final String catalog;
+    private final String schema;
+
+    private final DatabaseMetaData metaData;
+    private final DbAdapter adapter;
+
+    public DbAttributesBaseLoader(String catalog, String schema, DatabaseMetaData metaData, DbAdapter adapter) {
+        this.catalog = catalog;
+        this.schema = schema;
+        this.metaData = metaData;
+        this.adapter = adapter;
+    }
+
+    protected DbAttribute loadDbAttribute(Set<String> columns, ResultSet rs) throws SQLException {
+
+        // gets attribute's (column's) information
+        int columnType = rs.getInt("DATA_TYPE");
+
+        // ignore precision of non-decimal columns
+        int decimalDigits = -1;
+        if (TypesMapping.isDecimal(columnType)) {
+            decimalDigits = rs.getInt("DECIMAL_DIGITS");
+            if (rs.wasNull()) {
+                decimalDigits = -1;
+            }
+        }
+
+        // create attribute delegating this task to adapter
+        DbAttribute attr = adapter.buildAttribute(
+                rs.getString("COLUMN_NAME"),
+                rs.getString("TYPE_NAME"),
+                columnType,
+                rs.getInt("COLUMN_SIZE"),
+                decimalDigits,
+                rs.getBoolean("NULLABLE"));
+
+        if (columns.contains("IS_AUTOINCREMENT")) {
+            String autoIncrement = rs.getString("IS_AUTOINCREMENT");
+            if ("YES".equals(autoIncrement)) {
+                attr.setGenerated(true);
+            }
+        }
+        return attr;
+    }
+
+    @Override
+    public void loadDbAttributes(DbEntity entity) {
+        for (DbAttribute attr : loadDbAttributes(entity.getName())) {
+            attr.setEntity(entity);
+
+            // override existing attributes if it comes again
+            if (entity.getAttribute(attr.getName()) != null) {
+                entity.removeAttribute(attr.getName());
+            }
+            entity.addAttribute(attr);
+        }
+    }
+
+    protected abstract List<DbAttribute> loadDbAttributes(String tableName);
+
+    protected String getCatalog() {
+        return catalog;
+    }
+
+    protected String getSchema() {
+        return schema;
+    }
+
+    protected DatabaseMetaData getMetaData() {
+        return metaData;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesLoader.java
new file mode 100644
index 0000000..6658cdc
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesLoader.java
@@ -0,0 +1,43 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.reverse;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Interface responsible for attributes loading. Several options possible here
+ *  1) load attributes for each table separately
+ *  2) load attributes for schema and group it by table names
+ *
+ *  here is a trade of between count of queries and amount af calculation.
+ *
+ *
+ * @since 4.0
+ */
+public interface DbAttributesLoader {
+
+    // TODO use instant field for logging
+    Log LOGGER = LogFactory.getLog(DbTableLoader.class);
+
+    void loadDbAttributes(DbEntity entity);
+
+}
+

http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesPerSchemaLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesPerSchemaLoader.java b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesPerSchemaLoader.java
new file mode 100644
index 0000000..83ce60b
--- /dev/null
+++ b/cayenne-dbsync/src/main/java/org/apache/cayenne/dbsync/reverse/DbAttributesPerSchemaLoader.java
@@ -0,0 +1,130 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.reverse;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.DbAttribute;
+
+/**
+ * Load all attributes for schema and return it for each table
+ * */
+public class DbAttributesPerSchemaLoader extends DbAttributesBaseLoader {
+
+	private final TableFilter filter;
+
+	private Map<String, List<DbAttribute>> attributes;
+
+	public DbAttributesPerSchemaLoader(String catalog, String schema, DatabaseMetaData metaData, DbAdapter adapter,
+			TableFilter filter) {
+		super(catalog, schema, metaData, adapter);
+
+		this.filter = filter;
+	}
+
+	private Map<String, List<DbAttribute>> loadDbAttributes() throws SQLException {
+		Map<String, List<DbAttribute>> attributes = new HashMap<>();
+
+		try (ResultSet rs = getMetaData().getColumns(getCatalog(), getSchema(), "%", "%");) {
+			Set<String> columns = new HashSet<String>();
+
+			while (rs.next()) {
+				if (columns.isEmpty()) {
+					ResultSetMetaData rsMetaData = rs.getMetaData();
+					for (int i = 1; i <= rsMetaData.getColumnCount(); i++) {
+						columns.add(rsMetaData.getColumnLabel(i));
+					}
+				}
+
+				// for a reason not quiet apparent to me, Oracle sometimes
+				// returns duplicate record sets for the same table, messing up
+				// table
+				// names. E.g. for the system table "WK$_ATTR_MAPPING" columns
+				// are
+				// returned twice - as "WK$_ATTR_MAPPING" and
+				// "WK$$_ATTR_MAPPING"... Go figure
+				String tableName = rs.getString("TABLE_NAME");
+				String columnName = rs.getString("COLUMN_NAME");
+
+				PatternFilter columnFilter = filter.isIncludeTable(tableName);
+				/*
+				 * Here is possible optimization if filter will contain
+				 * map<tableName, columnFilter> we can replace it after tables
+				 * loading since already done pattern matching once and exactly
+				 * know all tables that we want to process
+				 */
+				if (columnFilter == null || !columnFilter.isInclude(columnName)) {
+					if (LOGGER.isDebugEnabled()) {
+						LOGGER.debug("Skip column '" + tableName + "." + columnName + "' (Path: " + getCatalog() + "/"
+								+ getSchema() + "; Filter: " + columnFilter + ")");
+					}
+					continue;
+				}
+
+				List<DbAttribute> attrs = attributes.get(tableName);
+				if (attrs == null) {
+					attrs = new LinkedList<DbAttribute>();
+
+					attributes.put(tableName, attrs);
+				}
+
+				attrs.add(loadDbAttribute(columns, rs));
+			}
+		}
+
+		return attributes;
+	}
+
+	@Override
+	protected List<DbAttribute> loadDbAttributes(String tableName) {
+		Map<String, List<DbAttribute>> attributes = getAttributes();
+		if (attributes != null) {
+			List<DbAttribute> dbAttributes = attributes.get(tableName);
+			if (dbAttributes != null) {
+				return dbAttributes;
+			}
+		}
+
+		return new LinkedList<DbAttribute>();
+	}
+
+	public Map<String, List<DbAttribute>> getAttributes() {
+		if (attributes == null) {
+			try {
+				attributes = loadDbAttributes();
+			} catch (SQLException e) {
+				LOGGER.error(e);
+				attributes = new HashMap<>();
+			}
+		}
+		return attributes;
+	}
+}