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:56 UTC
[10/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/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
new file mode 100644
index 0000000..c2357b0
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergeCase.java
@@ -0,0 +1,209 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
+import org.apache.cayenne.dbsync.reverse.DbLoaderConfiguration;
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.dbsync.unit.DbSyncCase;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.unit.UnitDbAdapter;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.junit.Before;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public abstract class MergeCase extends DbSyncCase {
+
+ @Inject
+ protected EntityResolver resolver;
+ @Inject
+ protected DataNode node;
+ protected DataMap map;
+ private Log logger = LogFactory.getLog(MergeCase.class);
+ @Inject
+ private DBHelper dbHelper;
+ @Inject
+ private ServerRuntime runtime;
+ @Inject
+ private UnitDbAdapter accessStackAdapter;
+ @Inject
+ private ServerCaseDataSourceFactory dataSourceFactory;
+
+ @Override
+ public void cleanUpDB() throws Exception {
+ dbHelper.update("ARTGROUP").set("PARENT_GROUP_ID", null, Types.INTEGER).execute();
+ super.cleanUpDB();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+
+ // this map can't be safely modified in this test, as it is reset by DI
+ // container
+ // on every test
+ map = runtime.getDataDomain().getDataMap("testmap");
+
+ filterDataMap();
+
+ List<MergerToken> tokens = createMergeTokens();
+ execute(tokens);
+
+ assertTokensAndExecute(0, 0);
+ }
+
+ protected DbMerger createMerger() {
+ return createMerger(null);
+ }
+
+ protected DbMerger createMerger(ValueForNullProvider valueForNullProvider) {
+ return new DbMerger(mergerFactory(), valueForNullProvider);
+ }
+
+ protected List<MergerToken> createMergeTokens() {
+ DbLoaderConfiguration loaderConfiguration = new DbLoaderConfiguration();
+ loaderConfiguration.setFiltersConfig(FiltersConfig.create(null, null,
+ TableFilter.include("ARTIST|GALLERY|PAINTING|NEW_TABLE2?"), PatternFilter.INCLUDE_NOTHING));
+
+ return createMerger().createMergeTokens(node.getDataSource(), node.getAdapter(), map, loaderConfiguration);
+ }
+
+ /**
+ * Remote binary pk {@link DbEntity} for {@link DbAdapter} not supporting
+ * that and so on.
+ */
+ private void filterDataMap() {
+ // copied from AbstractAccessStack.dbEntitiesInInsertOrder
+ boolean excludeBinPK = accessStackAdapter.supportsBinaryPK();
+
+ if (!excludeBinPK) {
+ return;
+ }
+
+ List<DbEntity> entitiesToRemove = new ArrayList<DbEntity>();
+
+ for (DbEntity ent : map.getDbEntities()) {
+ for (DbAttribute attr : ent.getAttributes()) {
+ // check for BIN PK or FK to BIN Pk
+ if (attr.getType() == Types.BINARY || attr.getType() == Types.VARBINARY
+ || attr.getType() == Types.LONGVARBINARY) {
+
+ if (attr.isPrimaryKey() || attr.isForeignKey()) {
+ entitiesToRemove.add(ent);
+ break;
+ }
+ }
+ }
+ }
+
+ for (DbEntity e : entitiesToRemove) {
+ map.removeDbEntity(e.getName(), true);
+ }
+ }
+
+ protected void execute(List<MergerToken> tokens) {
+ MergerContext mergerContext = MergerContext.builder(map).dataNode(node).build();
+ for (MergerToken tok : tokens) {
+ tok.execute(mergerContext);
+ }
+ }
+
+ protected void execute(MergerToken token) throws Exception {
+ MergerContext mergerContext = MergerContext.builder(map).dataNode(node).build();
+ token.execute(mergerContext);
+ }
+
+ private void executeSql(String sql) throws Exception {
+
+ try (Connection conn = dataSourceFactory.getSharedDataSource().getConnection();) {
+
+ try (Statement st = conn.createStatement();) {
+ st.execute(sql);
+ }
+ }
+ }
+
+ protected void assertTokens(List<MergerToken> tokens, int expectedToDb, int expectedToModel) {
+ int actualToDb = 0;
+ int actualToModel = 0;
+ for (MergerToken token : tokens) {
+ if (token.getDirection().isToDb()) {
+ actualToDb++;
+ } else if (token.getDirection().isToModel()) {
+ actualToModel++;
+ }
+ }
+
+ assertEquals("tokens to db", expectedToDb, actualToDb);
+ assertEquals("tokens to model", expectedToModel, actualToModel);
+ }
+
+ protected void assertTokensAndExecute(int expectedToDb, int expectedToModel) {
+ List<MergerToken> tokens = createMergeTokens();
+ assertTokens(tokens, expectedToDb, expectedToModel);
+ execute(tokens);
+ }
+
+ protected MergerTokenFactory mergerFactory() {
+ return runtime.getInjector().getInstance(MergerTokenFactoryProvider.class).get(node.getAdapter());
+ }
+
+ protected void dropTableIfPresent(String tableName) throws Exception {
+
+ // must have a dummy datamap for the dummy table for the downstream code
+ // to work
+ DataMap map = new DataMap("dummy");
+ map.setQuotingSQLIdentifiers(map.isQuotingSQLIdentifiers());
+ DbEntity entity = new DbEntity(tableName);
+ map.addDbEntity(entity);
+
+ AbstractToDbToken t = (AbstractToDbToken) mergerFactory().createDropTableToDb(entity);
+
+ for (String sql : t.createSql(node.getAdapter())) {
+
+ try {
+ executeSql(sql);
+ } catch (Exception e) {
+ logger.info("Exception dropping table " + tableName + ", probably abscent..");
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergerFactoryIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergerFactoryIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergerFactoryIT.java
new file mode 100644
index 0000000..1fe7da7
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/MergerFactoryIT.java
@@ -0,0 +1,310 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.sql.Types;
+
+import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+
+public class MergerFactoryIT extends MergeCase {
+
+ @Inject
+ private DataContext context;
+
+ @Test
+ public void testAddAndDropColumnToDb() throws Exception {
+ DbEntity dbEntity = map.getDbEntity("PAINTING");
+ assertNotNull(dbEntity);
+
+ // create and add new column to model and db
+ DbAttribute column = new DbAttribute("NEWCOL1", Types.VARCHAR, dbEntity);
+
+ column.setMandatory(false);
+ column.setMaxLength(10);
+ dbEntity.addAttribute(column);
+ assertTokensAndExecute(1, 0);
+
+ // try merge once more to check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // remove it from model and db
+ dbEntity.removeAttribute(column.getName());
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+ }
+
+ @Test
+ public void testChangeVarcharSizeToDb() throws Exception {
+ DbEntity dbEntity = map.getDbEntity("PAINTING");
+ assertNotNull(dbEntity);
+
+ // create and add new column to model and db
+ DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
+
+ column.setMandatory(false);
+ column.setMaxLength(10);
+ dbEntity.addAttribute(column);
+ assertTokensAndExecute(1, 0);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // change size
+ column.setMaxLength(20);
+
+ // merge to db
+ assertTokensAndExecute(1, 0);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // clean up
+ dbEntity.removeAttribute(column.getName());
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+ }
+
+ @Test
+ public void testMultipleTokensToDb() throws Exception {
+ DbEntity dbEntity = map.getDbEntity("PAINTING");
+ assertNotNull(dbEntity);
+
+ DbAttribute column1 = new DbAttribute("NEWCOL3", Types.VARCHAR, dbEntity);
+ column1.setMandatory(false);
+ column1.setMaxLength(10);
+ dbEntity.addAttribute(column1);
+ DbAttribute column2 = new DbAttribute("NEWCOL4", Types.VARCHAR, dbEntity);
+ column2.setMandatory(false);
+ column2.setMaxLength(10);
+ dbEntity.addAttribute(column2);
+
+ assertTokensAndExecute(2, 0);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // change size
+ column1.setMaxLength(20);
+ column2.setMaxLength(30);
+
+ // merge to db
+ assertTokensAndExecute(2, 0);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // clean up
+ dbEntity.removeAttribute(column1.getName());
+ dbEntity.removeAttribute(column2.getName());
+ assertTokensAndExecute(2, 0);
+ assertTokensAndExecute(0, 0);
+ }
+
+ @Test
+ public void testAddTableToDb() throws Exception {
+ dropTableIfPresent("NEW_TABLE");
+
+ assertTokensAndExecute(0, 0);
+
+ DbEntity dbEntity = new DbEntity("NEW_TABLE");
+
+ DbAttribute column1 = new DbAttribute("ID", Types.INTEGER, dbEntity);
+ column1.setMandatory(true);
+ column1.setPrimaryKey(true);
+ dbEntity.addAttribute(column1);
+
+ DbAttribute column2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity);
+ column2.setMaxLength(10);
+ column2.setMandatory(false);
+ dbEntity.addAttribute(column2);
+
+ map.addDbEntity(dbEntity);
+
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+
+ ObjEntity objEntity = new ObjEntity("NewTable");
+ objEntity.setDbEntity(dbEntity);
+ ObjAttribute oatr1 = new ObjAttribute("name");
+ oatr1.setDbAttributePath(column2.getName());
+ oatr1.setType("java.lang.String");
+ objEntity.addAttribute(oatr1);
+ map.addObjEntity(objEntity);
+
+ for (int i = 0; i < 5; i++) {
+ CayenneDataObject dao = (CayenneDataObject) context.newObject(objEntity
+ .getName());
+ dao.writeProperty(oatr1.getName(), "test " + i);
+ }
+ context.commitChanges();
+
+ // clear up
+ map.removeObjEntity(objEntity.getName(), true);
+ map.removeDbEntity(dbEntity.getName(), true);
+ resolver.refreshMappingCache();
+ assertNull(map.getObjEntity(objEntity.getName()));
+ assertNull(map.getDbEntity(dbEntity.getName()));
+ assertFalse(map.getDbEntities().contains(dbEntity));
+
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+ }
+
+ @Test
+ public void testAddForeignKeyWithTable() throws Exception {
+ dropTableIfPresent("NEW_TABLE");
+
+ assertTokensAndExecute(0, 0);
+
+ DbEntity dbEntity = new DbEntity("NEW_TABLE");
+
+ attr(dbEntity, "ID", Types.INTEGER, true, true);
+ attr(dbEntity, "NAME", Types.VARCHAR, false, false).setMaxLength(10);
+ attr(dbEntity, "ARTIST_ID", Types.BIGINT, false, false);
+
+ map.addDbEntity(dbEntity);
+
+ DbEntity artistDbEntity = map.getDbEntity("ARTIST");
+ assertNotNull(artistDbEntity);
+
+ // relation from new_table to artist
+ DbRelationship r1 = new DbRelationship("toArtistR1");
+ r1.setSourceEntity(dbEntity);
+ r1.setTargetEntityName(artistDbEntity);
+ r1.setToMany(false);
+ r1.addJoin(new DbJoin(r1, "ARTIST_ID", "ARTIST_ID"));
+ dbEntity.addRelationship(r1);
+
+ // relation from artist to new_table
+ DbRelationship r2 = new DbRelationship("toNewTableR2");
+ r2.setSourceEntity(artistDbEntity);
+ r2.setTargetEntityName(dbEntity);
+ r2.setToMany(true);
+ r2.addJoin(new DbJoin(r2, "ARTIST_ID", "ARTIST_ID"));
+ artistDbEntity.addRelationship(r2);
+
+ assertTokensAndExecute(2, 0);
+ assertTokensAndExecute(0, 0);
+
+ // remove relationships
+ dbEntity.removeRelationship(r1.getName());
+ artistDbEntity.removeRelationship(r2.getName());
+ resolver.refreshMappingCache();
+ /*
+ * Db -Rel 'toArtistR1' - NEW_TABLE 1 -> 1 ARTIST"
+r2 = * Db -Rel 'toNewTableR2' - ARTIST 1 -> * NEW_TABLE"
+ * */
+ assertTokensAndExecute(1, 1);
+ assertTokensAndExecute(0, 0);
+
+ // clear up
+ // map.removeObjEntity(objEntity.getName(), true);
+ map.removeDbEntity(dbEntity.getName(), true);
+ resolver.refreshMappingCache();
+ // assertNull(map.getObjEntity(objEntity.getName()));
+ assertNull(map.getDbEntity(dbEntity.getName()));
+ assertFalse(map.getDbEntities().contains(dbEntity));
+
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+ }
+
+ @Test
+ public void testAddForeignKeyAfterTable() throws Exception {
+ dropTableIfPresent("NEW_TABLE");
+
+ assertTokensAndExecute(0, 0);
+
+ DbEntity dbEntity = new DbEntity("NEW_TABLE");
+ attr(dbEntity, "ID", Types.INTEGER, true, true);
+ attr(dbEntity, "NAME", Types.VARCHAR, false, false).setMaxLength(10);
+ attr(dbEntity, "ARTIST_ID", Types.BIGINT, false, false);
+
+ map.addDbEntity(dbEntity);
+
+ DbEntity artistDbEntity = map.getDbEntity("ARTIST");
+ assertNotNull(artistDbEntity);
+
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+
+ // relation from new_table to artist
+ DbRelationship r1 = new DbRelationship("toArtistR1");
+ r1.setSourceEntity(dbEntity);
+ r1.setTargetEntityName(artistDbEntity);
+ r1.setToMany(false);
+ r1.addJoin(new DbJoin(r1, "ARTIST_ID", "ARTIST_ID"));
+ dbEntity.addRelationship(r1);
+
+ // relation from artist to new_table
+ DbRelationship r2 = new DbRelationship("toNewTableR2");
+ r2.setSourceEntity(artistDbEntity);
+ r2.setTargetEntityName(dbEntity);
+ r2.setToMany(true);
+ r2.addJoin(new DbJoin(r2, "ARTIST_ID", "ARTIST_ID"));
+ artistDbEntity.addRelationship(r2);
+
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+
+ // remove relationships
+ dbEntity.removeRelationship(r1.getName());
+ artistDbEntity.removeRelationship(r2.getName());
+ resolver.refreshMappingCache();
+ /*
+ * Add Relationship ARTIST->NEW_TABLE To Model
+ * Drop Relationship NEW_TABLE->ARTIST To DB
+ * */
+ assertTokensAndExecute(1, 1);
+ assertTokensAndExecute(0, 0);
+
+ // clear up
+ // map.removeObjEntity(objEntity.getName(), true);
+ map.removeDbEntity(dbEntity.getName(), true);
+ resolver.refreshMappingCache();
+ // assertNull(map.getObjEntity(objEntity.getName()));
+ assertNull(map.getDbEntity(dbEntity.getName()));
+ assertFalse(map.getDbEntities().contains(dbEntity));
+
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+ }
+
+ private static DbAttribute attr(DbEntity dbEntity, String name, int type, boolean mandatory, boolean primaryKey) {
+ DbAttribute column1 = new DbAttribute(name, type, dbEntity);
+ column1.setMandatory(mandatory);
+ column1.setPrimaryKey(primaryKey);
+
+ dbEntity.addAttribute(column1);
+ return column1;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDbIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDbIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDbIT.java
new file mode 100644
index 0000000..6391ad0
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetAllowNullToDbIT.java
@@ -0,0 +1,66 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.sql.Types;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Test;
+
+public class SetAllowNullToDbIT extends MergeCase {
+
+ @Test
+ public void test() throws Exception {
+ DbEntity dbEntity = map.getDbEntity("PAINTING");
+ assertNotNull(dbEntity);
+
+ // create and add new column to model and db
+ DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
+
+ try {
+
+ column.setMandatory(true);
+ column.setMaxLength(10);
+ dbEntity.addAttribute(column);
+ assertTokensAndExecute(2, 0);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // set null
+ column.setMandatory(false);
+
+ // merge to db
+ assertTokensAndExecute(1, 0);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // clean up
+ } finally {
+ dbEntity.removeAttribute(column.getName());
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetNotNullToDbIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetNotNullToDbIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetNotNullToDbIT.java
new file mode 100644
index 0000000..508d0f8
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetNotNullToDbIT.java
@@ -0,0 +1,62 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.sql.Types;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Test;
+
+public class SetNotNullToDbIT extends MergeCase {
+
+ @Test
+ public void test() throws Exception {
+ DbEntity dbEntity = map.getDbEntity("PAINTING");
+ assertNotNull(dbEntity);
+
+ // create and add new column to model and db
+ DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
+
+ column.setMandatory(false);
+ column.setMaxLength(10);
+ dbEntity.addAttribute(column);
+ assertTokensAndExecute(1, 0);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // set not null
+ column.setMandatory(true);
+
+ // merge to db
+ assertTokensAndExecute(1, 0);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // clean up
+ dbEntity.removeAttribute(column.getName());
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDbIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDbIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDbIT.java
new file mode 100644
index 0000000..3b513e7
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/SetPrimaryKeyToDbIT.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import java.sql.Types;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Test;
+
+public class SetPrimaryKeyToDbIT extends MergeCase {
+
+ @Test
+ public void test() throws Exception {
+ dropTableIfPresent("NEW_TABLE");
+ assertTokensAndExecute(0, 0);
+
+ DbEntity dbEntity1 = new DbEntity("NEW_TABLE");
+
+ DbAttribute e1col1 = new DbAttribute("ID1", Types.INTEGER, dbEntity1);
+ e1col1.setMandatory(true);
+ e1col1.setPrimaryKey(true);
+ dbEntity1.addAttribute(e1col1);
+ map.addDbEntity(dbEntity1);
+
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+
+ DbAttribute e1col2 = new DbAttribute("ID2", Types.INTEGER, dbEntity1);
+ e1col2.setMandatory(true);
+ dbEntity1.addAttribute(e1col2);
+
+ assertTokensAndExecute(2, 0);
+ assertTokensAndExecute(0, 0);
+
+ e1col1.setPrimaryKey(false);
+ e1col2.setPrimaryKey(true);
+
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensReversTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensReversTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensReversTest.java
new file mode 100644
index 0000000..b23128c
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensReversTest.java
@@ -0,0 +1,89 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import org.apache.cayenne.dbsync.merge.factory.HSQLMergerTokenFactory;
+import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Collections;
+
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbAttr;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbEntity;
+
+/**
+ * @since 4.0.
+ */
+public class TokensReversTest {
+
+ @Test
+ public void testReverses() {
+ DbAttribute attr = dbAttr().build();
+ DbEntity entity = dbEntity().attributes(attr).build();
+ DbRelationship rel = new DbRelationship("rel");
+ rel.setSourceEntity(entity);
+ rel.addJoin(new DbJoin(rel, attr.getName(), "dontKnow"));
+
+ test(factory().createAddColumnToDb(entity, attr));
+ test(factory().createAddColumnToModel(entity, attr));
+ test(factory().createDropColumnToDb(entity, attr));
+ test(factory().createDropColumnToModel(entity, attr));
+
+ test(factory().createAddRelationshipToDb(entity, rel));
+ test(factory().createAddRelationshipToModel(entity, rel));
+ test(factory().createDropRelationshipToDb(entity, rel));
+ test(factory().createDropRelationshipToModel(entity, rel));
+
+ test(factory().createCreateTableToDb(entity));
+ test(factory().createCreateTableToModel(entity));
+ test(factory().createDropTableToDb(entity));
+ test(factory().createDropTableToModel(entity));
+
+ test(factory().createSetAllowNullToDb(entity, attr));
+ test(factory().createSetAllowNullToModel(entity, attr));
+ test(factory().createSetNotNullToDb(entity, attr));
+ test(factory().createSetNotNullToModel(entity, attr));
+
+ DbAttribute attr2 = dbAttr().build();
+ test(factory().createSetColumnTypeToDb(entity, attr, attr2));
+ test(factory().createSetColumnTypeToModel(entity, attr, attr2));
+
+ test(factory().createSetPrimaryKeyToDb(entity, Collections.singleton(attr), Collections.singleton(attr2), "PK"));
+ test(factory().createSetPrimaryKeyToModel(entity, Collections.singleton(attr), Collections.singleton(attr2), "PK"));
+
+ test(factory().createSetValueForNullToDb(entity, attr, new DefaultValueForNullProvider()));
+ }
+
+ private void test(MergerToken token1) {
+ MergerToken token2 = token1.createReverse(factory()).createReverse(factory());
+
+ Assert.assertEquals(token1.getTokenName(), token2.getTokenName());
+ Assert.assertEquals(token1.getTokenValue(), token2.getTokenValue());
+ Assert.assertEquals(token1.getDirection(), token2.getDirection());
+ }
+
+ private MergerTokenFactory factory() {
+ return new HSQLMergerTokenFactory();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensToModelExecutionTest.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensToModelExecutionTest.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensToModelExecutionTest.java
new file mode 100644
index 0000000..b9abea1
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/TokensToModelExecutionTest.java
@@ -0,0 +1,80 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.dbsync.merge.factory.DefaultMergerTokenFactory;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.junit.Test;
+
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dataMap;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbAttr;
+import static org.apache.cayenne.dbsync.merge.builders.ObjectMother.dbEntity;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @since 4.0.
+ */
+public class TokensToModelExecutionTest {
+
+ @Test
+ public void testCreateAndDropTable() throws Exception {
+ DbEntity entity = dbEntity().build();
+
+ DataMap dataMap = dataMap().build();
+ assertTrue(dataMap.getDbEntityMap().isEmpty());
+ assertTrue(dataMap.getObjEntityMap().isEmpty());
+
+ MergerContext context = MergerContext.builder(dataMap).dataNode(new DataNode()).build();
+ new DefaultMergerTokenFactory().createCreateTableToModel(entity).execute(context);
+
+ assertEquals(1, dataMap.getDbEntityMap().size());
+ assertEquals(1, dataMap.getObjEntities().size());
+ assertEquals(entity, dataMap.getDbEntity(entity.getName()));
+
+ new DefaultMergerTokenFactory().createDropTableToModel(entity).execute(context);
+ assertTrue(dataMap.getDbEntityMap().isEmpty());
+ assertTrue(dataMap.getObjEntityMap().isEmpty());
+ }
+
+ @Test
+ public void testCreateAndDropColumn() throws Exception {
+ DbAttribute attr = dbAttr("attr").build();
+ DbEntity entity = dbEntity().build();
+
+ DataMap dataMap = dataMap().with(entity).build();
+ assertEquals(1, dataMap.getDbEntityMap().size());
+ assertTrue(dataMap.getObjEntityMap().isEmpty());
+
+ MergerContext context = MergerContext.builder(dataMap).dataNode(new DataNode()).build();
+ new DefaultMergerTokenFactory().createAddColumnToModel(entity, attr).execute(context);
+
+ assertEquals(1, dataMap.getDbEntityMap().size());
+ assertEquals(1, entity.getAttributes().size());
+ assertEquals(attr, entity.getAttribute(attr.getName()));
+
+ new DefaultMergerTokenFactory().createDropColumnToModel(entity, attr).execute(context);
+ assertEquals(1, dataMap.getDbEntityMap().size());
+ assertTrue(entity.getAttributes().isEmpty());
+ assertTrue(dataMap.getObjEntityMap().isEmpty());
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/ValueForNullIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/ValueForNullIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/ValueForNullIT.java
new file mode 100644
index 0000000..e9910fd
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/ValueForNullIT.java
@@ -0,0 +1,127 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge;
+
+import junit.framework.AssertionFailedError;
+import org.apache.cayenne.DataObject;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.access.jdbc.SQLParameterBinding;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.query.SelectQuery;
+import org.junit.Test;
+
+import java.sql.Types;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ValueForNullIT extends MergeCase {
+
+ private static final String DEFAULT_VALUE_STRING = "DEFSTRING";
+
+ @Inject
+ private DataContext context;
+
+ @Test
+ public void test() throws Exception {
+ DbEntity dbEntity = map.getDbEntity("PAINTING");
+ assertNotNull(dbEntity);
+ ObjEntity objEntity = map.getObjEntity("Painting");
+ assertNotNull(objEntity);
+
+ // insert some rows before adding "not null" column
+ final int nrows = 10;
+ for (int i = 0; i < nrows; i++) {
+ DataObject o = (DataObject) context.newObject("Painting");
+ o.writeProperty("paintingTitle", "ptitle" + i);
+ }
+ context.commitChanges();
+
+ // create and add new column to model and db
+ DbAttribute column = new DbAttribute("NEWCOL2", Types.VARCHAR, dbEntity);
+
+ column.setMandatory(false);
+ column.setMaxLength(10);
+ dbEntity.addAttribute(column);
+ assertTrue(dbEntity.getAttributes().contains(column));
+ assertEquals(column, dbEntity.getAttribute(column.getName()));
+ assertTokensAndExecute(1, 0);
+
+ // need obj attr to be able to query
+ ObjAttribute objAttr = new ObjAttribute("newcol2");
+ objAttr.setDbAttributePath(column.getName());
+ objEntity.addAttribute(objAttr);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // set not null
+ column.setMandatory(true);
+
+ // merge to db
+ assertTokensAndExecute(2, 0);
+
+ // check that is was merged
+ assertTokensAndExecute(0, 0);
+
+ // check values for null
+ Expression qual = ExpressionFactory.matchExp(objAttr.getName(), DEFAULT_VALUE_STRING);
+ SelectQuery query = new SelectQuery("Painting", qual);
+ List<Persistent> rows = context.performQuery(query);
+ assertEquals(nrows, rows.size());
+
+ // clean up
+ dbEntity.removeAttribute(column.getName());
+ assertTokensAndExecute(1, 0);
+ assertTokensAndExecute(0, 0);
+ }
+
+ @Override
+ protected DbMerger createMerger(final ValueForNullProvider valueForNullProvider) {
+ return super.createMerger(new DefaultValueForNullProvider() {
+
+ @Override
+ protected SQLParameterBinding get(DbEntity entity, DbAttribute column) {
+ int type = column.getType();
+ switch (type) {
+ case Types.VARCHAR:
+ return new SQLParameterBinding(DEFAULT_VALUE_STRING, type, -1);
+ default:
+ throw new AssertionFailedError("should not get here");
+ }
+ }
+
+ @Override
+ public boolean hasValueFor(DbEntity entity, DbAttribute column) {
+ return true;
+ }
+
+ });
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/Builder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/Builder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/Builder.java
new file mode 100644
index 0000000..baf8240
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/Builder.java
@@ -0,0 +1,38 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.builders;
+
+/**
+ * Base interface for all domain builders
+ *
+ * @since 4.0.
+ */
+public interface Builder<T> {
+
+ /**
+ * Build valid object. If some required data omitted it will be filled with random data.
+ * */
+ T build();
+
+ /**
+ * Build valid object and add some optional fields randomly.
+ * */
+ T random();
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DataMapBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DataMapBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DataMapBuilder.java
new file mode 100644
index 0000000..a7aa11c
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DataMapBuilder.java
@@ -0,0 +1,128 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.builders;
+
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.map.ObjEntity;
+
+import java.util.Collections;
+
+/**
+ * @since 4.0.
+ */
+public class DataMapBuilder extends DefaultBuilder<DataMap> {
+
+ public DataMapBuilder() {
+ this(new DataMap());
+ }
+
+ public DataMapBuilder(DataMap dataMap) {
+ super(dataMap);
+ }
+
+ public DataMapBuilder with(DbEntity ... entities) {
+ for (DbEntity entity : entities) {
+ obj.addDbEntity(entity);
+ }
+
+ return this;
+ }
+
+ public DataMapBuilder with(DbEntityBuilder ... entities) {
+ for (DbEntityBuilder entity : entities) {
+ obj.addDbEntity(entity.build());
+ }
+
+ return this;
+ }
+
+ public DataMapBuilder withDbEntities(int count) {
+ for (int i = 0; i < count; i++) {
+ obj.addDbEntity(ObjectMother.dbEntity().random());
+ }
+
+ return this;
+ }
+
+ public DataMapBuilder with(ObjEntity... entities) {
+ for (ObjEntity entity : entities) {
+ obj.addObjEntity(entity);
+ }
+
+ return this;
+ }
+
+ public DataMapBuilder with(ObjEntityBuilder ... entities) {
+ for (ObjEntityBuilder entity : entities) {
+ obj.addObjEntity(entity.build());
+ }
+
+ return this;
+ }
+
+ public DataMapBuilder withObjEntities(int count) {
+ for (int i = 0; i < count; i++) {
+ obj.addObjEntity(ObjectMother.objEntity().random());
+ }
+
+ return this;
+ }
+
+ public DataMapBuilder join(String from, String to) {
+ return join(null, from, to);
+ }
+
+ public DataMapBuilder join(String name, String from, String to) {
+ String[] fromSplit = from.split("\\.");
+ DbEntity fromEntity = obj.getDbEntity(fromSplit[0]);
+ if (fromEntity == null) {
+ throw new IllegalArgumentException("Entity '" + fromSplit[0] + "' is undefined");
+ }
+
+ String[] toSplit = to.split("\\.");
+
+ fromEntity.addRelationship(new DbRelationshipBuilder(name)
+ .from(fromEntity, fromSplit[1])
+ .to(toSplit[0], toSplit[1])
+
+ .build());
+
+ return this;
+ }
+
+ public DataMap build() {
+ if (obj.getNamespace() == null) {
+ obj.setNamespace(new EntityResolver(Collections.singleton(obj)));
+ }
+
+ return obj;
+ }
+
+ @Override
+ public DataMap random() {
+ if (dataFactory.chance(90)) {
+ withDbEntities(dataFactory.getNumberUpTo(10));
+ }
+
+
+ return build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbAttributeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbAttributeBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbAttributeBuilder.java
new file mode 100644
index 0000000..2d600f3
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbAttributeBuilder.java
@@ -0,0 +1,115 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.builders;
+
+import org.apache.cayenne.datafactory.DictionaryValueProvider;
+import org.apache.cayenne.datafactory.ValueProvider;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.map.DbAttribute;
+
+import static org.apache.commons.lang.StringUtils.isEmpty;
+
+/**
+ * @since 4.0.
+ */
+public class DbAttributeBuilder extends DefaultBuilder<DbAttribute> {
+
+ private static final ValueProvider<String> TYPES_RANDOM = new DictionaryValueProvider<String>(ValueProvider.RANDOM) {
+ @Override
+ protected String[] values() {
+ return TypesMapping.getDatabaseTypes();
+ }
+ };
+
+ public DbAttributeBuilder() {
+ super(new DbAttribute());
+ }
+
+ public DbAttributeBuilder name() {
+ return name(getRandomJavaName());
+ }
+
+ public DbAttributeBuilder name(String name) {
+ obj.setName(name);
+
+ return this;
+ }
+
+ public DbAttributeBuilder type() {
+ return type(TYPES_RANDOM.randomValue());
+ }
+
+ public DbAttributeBuilder type(String item) {
+ obj.setType(TypesMapping.getSqlTypeByName(item));
+
+ return this;
+ }
+
+ public DbAttributeBuilder typeInt() {
+ return type(TypesMapping.SQL_INTEGER);
+ }
+
+ public DbAttributeBuilder typeBigInt() {
+ return type(TypesMapping.SQL_BIGINT);
+ }
+
+ public DbAttributeBuilder typeVarchar(int length) {
+ type(TypesMapping.SQL_VARCHAR);
+ length(length);
+
+ return this;
+ }
+
+ private DbAttributeBuilder length(int length) {
+ obj.setMaxLength(length);
+
+ return this;
+ }
+
+ public DbAttributeBuilder primaryKey() {
+ obj.setPrimaryKey(true);
+
+ return this;
+ }
+
+ public DbAttributeBuilder mandatory() {
+ obj.setMandatory(true);
+
+ return this;
+ }
+
+ @Override
+ public DbAttribute build() {
+ if (isEmpty(obj.getName())) {
+ name();
+ }
+
+ if (obj.getType() == TypesMapping.NOT_DEFINED) {
+ type();
+ }
+
+ return obj;
+ }
+
+ @Override
+ public DbAttribute random() {
+ return build();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbEntityBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbEntityBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbEntityBuilder.java
new file mode 100644
index 0000000..03f0738
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbEntityBuilder.java
@@ -0,0 +1,90 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.builders;
+
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 4.0.
+ */
+public class DbEntityBuilder extends DefaultBuilder<DbEntity> {
+
+ public DbEntityBuilder() {
+ super(new DbEntity());
+ }
+
+ public DbEntityBuilder name() {
+ return name(getRandomJavaName());
+ }
+
+ public DbEntityBuilder name(String name) {
+ obj.setName(name);
+
+ return this;
+ }
+
+ public DbEntityBuilder attributes(DbAttribute ... attributes) {
+ for (DbAttribute attribute : attributes) {
+ obj.addAttribute(attribute);
+ }
+
+ return this;
+ }
+
+ public DbEntityBuilder attributes(DbAttributeBuilder ... attributes) {
+ for (DbAttributeBuilder attribute : attributes) {
+ obj.addAttribute(attribute.build());
+ }
+
+ return this;
+ }
+
+ public DbEntityBuilder attributes(int numberUpTo) {
+ for (int i = 0; i < numberUpTo; i++) {
+ try {
+ obj.addAttribute(new DbAttributeBuilder().random());
+ } catch (IllegalArgumentException e) {
+ i--; // try again
+ }
+ }
+
+ return this;
+ }
+
+
+ @Override
+ public DbEntity build() {
+ if (obj.getName() == null) {
+ obj.setName(StringUtils.capitalize(getRandomJavaName()));
+ }
+
+ return obj;
+ }
+
+ @Override
+ public DbEntity random() {
+ if (dataFactory.chance(99)) {
+ attributes(dataFactory.getNumberUpTo(20));
+ }
+
+ return build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbRelationshipBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbRelationshipBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbRelationshipBuilder.java
new file mode 100644
index 0000000..ae87549
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DbRelationshipBuilder.java
@@ -0,0 +1,85 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.builders;
+
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbJoin;
+import org.apache.cayenne.map.DbRelationship;
+
+/**
+ * @since 4.0.
+ */
+public class DbRelationshipBuilder extends DefaultBuilder<DbRelationship> {
+
+ private String[] from;
+ private String[] to;
+
+ public DbRelationshipBuilder() {
+ super(new DbRelationship());
+ }
+
+ public DbRelationshipBuilder(String name) {
+ super(new DbRelationship(name));
+ }
+
+ public DbRelationshipBuilder(DbRelationship obj) {
+ super(obj);
+ }
+
+ public DbRelationshipBuilder name() {
+ return name(getRandomJavaName());
+ }
+
+ public DbRelationshipBuilder name(String name) {
+ obj.setName(name);
+
+ return this;
+ }
+
+ public DbRelationshipBuilder from(DbEntity entity, String ... columns) {
+ obj.setSourceEntity(entity);
+ this.from = columns;
+
+ return this;
+ }
+
+ public DbRelationshipBuilder to(String entityName, String ... columns) {
+ obj.setTargetEntityName(entityName);
+ this.to = columns;
+
+ return this;
+ }
+
+ @Override
+ public DbRelationship build() {
+ if (obj.getName() == null) {
+ name();
+ }
+
+ if (from.length != to.length) {
+ throw new IllegalStateException("from and to columns name size mismatch");
+ }
+
+ for (int i = 0; i < from.length; i++) {
+ obj.addJoin(new DbJoin(obj, from[i], to[i]));
+ }
+
+ return obj;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DefaultBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DefaultBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DefaultBuilder.java
new file mode 100644
index 0000000..559347b
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/DefaultBuilder.java
@@ -0,0 +1,57 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.builders;
+
+import org.apache.cayenne.datafactory.DataFactory;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 4.0.
+ */
+public abstract class DefaultBuilder<T> implements Builder<T> {
+
+ protected final DataFactory dataFactory;
+ protected final T obj;
+
+
+ protected DefaultBuilder(T obj) {
+ this.dataFactory = new DataFactory();
+ this.obj = obj;
+ }
+
+ public String getRandomJavaName() {
+ int count = dataFactory.getNumberBetween(1, 5);
+ StringBuilder res = new StringBuilder();
+ for (int i = 0; i < count; i++) {
+ res.append(StringUtils.capitalize(dataFactory.getRandomWord()));
+ }
+
+ return StringUtils.uncapitalize(res.toString());
+ }
+
+ @Override
+ public T build() {
+ return obj;
+ }
+
+ @Override
+ public T random() {
+ return build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjAttributeBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjAttributeBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjAttributeBuilder.java
new file mode 100644
index 0000000..a183c34
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjAttributeBuilder.java
@@ -0,0 +1,67 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.builders;
+
+import org.apache.cayenne.map.ObjAttribute;
+
+/**
+ * @since 4.0.
+ */
+public class ObjAttributeBuilder extends DefaultBuilder<ObjAttribute> {
+
+ public ObjAttributeBuilder() {
+ super(new ObjAttribute());
+ }
+
+ public ObjAttributeBuilder name() {
+ return name(getRandomJavaName());
+ }
+
+ public ObjAttributeBuilder name(String name) {
+ obj.setName(name);
+
+ return this;
+ }
+
+ public ObjAttributeBuilder type(Class type) {
+ obj.setType(type.getCanonicalName());
+
+ return this;
+ }
+
+ public ObjAttributeBuilder dbPath(String path) {
+ obj.setDbAttributePath(path);
+
+ return this;
+ }
+
+ @Override
+ public ObjAttribute build() {
+ if (obj.getName() == null) {
+ name();
+ }
+
+ return obj;
+ }
+
+ @Override
+ public ObjAttribute random() {
+ return build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjEntityBuilder.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjEntityBuilder.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjEntityBuilder.java
new file mode 100644
index 0000000..f2f701c
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjEntityBuilder.java
@@ -0,0 +1,98 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.builders;
+
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @since 4.0.
+ */
+public class ObjEntityBuilder extends DefaultBuilder<ObjEntity> {
+
+ public ObjEntityBuilder() {
+ super(new ObjEntity());
+ }
+
+ public ObjEntityBuilder name() {
+ return name(getRandomJavaName());
+ }
+
+ public ObjEntityBuilder name(String name) {
+ obj.setName(name);
+
+ return this;
+ }
+
+ public ObjEntityBuilder attributes(ObjAttribute... attributes) {
+ for (ObjAttribute attribute : attributes) {
+ obj.addAttribute(attribute);
+ }
+
+ return this;
+ }
+
+ public ObjEntityBuilder attributes(ObjAttributeBuilder ... attributes) {
+ for (ObjAttributeBuilder attribute : attributes) {
+ obj.addAttribute(attribute.build());
+ }
+
+ return this;
+ }
+
+ public ObjEntityBuilder attributes(int numberUpTo) {
+ for (int i = 0; i < numberUpTo; i++) {
+ obj.addAttribute(new ObjAttributeBuilder().random());
+ }
+
+ return this;
+ }
+
+
+ @Override
+ public ObjEntity build() {
+ if (obj.getName() == null) {
+ obj.setName(StringUtils.capitalize(getRandomJavaName()));
+ }
+
+ return obj;
+ }
+
+ @Override
+ public ObjEntity random() {
+ if (dataFactory.chance(99)) {
+ attributes(dataFactory.getNumberUpTo(20));
+ }
+
+ return build();
+ }
+
+ public ObjEntityBuilder clazz(String s) {
+ obj.setClassName(s);
+
+ return this;
+ }
+
+ public ObjEntityBuilder dbEntity(String table) {
+ obj.setDbEntityName(table);
+
+ return this;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjectMother.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjectMother.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjectMother.java
new file mode 100644
index 0000000..0097fe1
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/merge/builders/ObjectMother.java
@@ -0,0 +1,70 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+package org.apache.cayenne.dbsync.merge.builders;
+
+import org.apache.cayenne.map.DataMap;
+
+/**
+ * Factory for test data see pattern definition:
+ * http://martinfowler.com/bliki/ObjectMother.html
+ *
+ * @since 4.0.
+ */
+public class ObjectMother {
+
+ public static DataMapBuilder dataMap() {
+ return new DataMapBuilder();
+ }
+
+ public static DataMapBuilder dataMap(DataMap dataMap) {
+ return new DataMapBuilder(dataMap);
+ }
+
+ public static DbEntityBuilder dbEntity() {
+ return new DbEntityBuilder();
+ }
+
+ public static DbEntityBuilder dbEntity(String name) {
+ return new DbEntityBuilder().name(name);
+ }
+
+ public static ObjEntityBuilder objEntity() {
+ return new ObjEntityBuilder();
+ }
+
+ public static ObjEntityBuilder objEntity(String packageName, String className, String table) {
+ return new ObjEntityBuilder()
+ .name(className)
+ .clazz(packageName + "." + className)
+ .dbEntity(table);
+ }
+
+ public static ObjAttributeBuilder objAttr(String name) {
+ return new ObjAttributeBuilder().name(name);
+ }
+
+ public static DbAttributeBuilder dbAttr(String name) {
+ return dbAttr().name(name);
+ }
+
+ public static DbAttributeBuilder dbAttr() {
+ return new DbAttributeBuilder();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/2f7b1d53/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderIT.java
----------------------------------------------------------------------
diff --git a/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderIT.java b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderIT.java
new file mode 100644
index 0000000..a902754
--- /dev/null
+++ b/cayenne-dbsync/src/test/java/org/apache/cayenne/dbsync/reverse/DbLoaderIT.java
@@ -0,0 +1,430 @@
+/*****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.dbsync.reverse;
+
+import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
+import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
+import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.dba.TypesMapping;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.*;
+import org.apache.cayenne.unit.UnitDbAdapter;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.ServerCaseDataSourceFactory;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Types;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class DbLoaderIT extends ServerCase {
+
+ public static final DbLoaderConfiguration CONFIG = new DbLoaderConfiguration();
+ @Inject
+ private ServerRuntime runtime;
+
+ @Inject
+ private DbAdapter adapter;
+
+ @Inject
+ private ServerCaseDataSourceFactory dataSourceFactory;
+
+ @Inject
+ private UnitDbAdapter accessStackAdapter;
+
+ private DbLoader loader;
+
+ @Before
+ public void setUp() throws Exception {
+ loader = new DbLoader(dataSourceFactory.getSharedDataSource().getConnection(), adapter, null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ loader.getConnection().close();
+ }
+
+ @Test
+ public void testGetTableTypes() throws Exception {
+
+ List<?> tableTypes = loader.getTableTypes();
+
+ assertNotNull(tableTypes);
+
+ String tableLabel = adapter.tableTypeForTable();
+ if (tableLabel != null) {
+ assertTrue("Missing type for table '" + tableLabel + "' - " + tableTypes, tableTypes.contains(tableLabel));
+ }
+
+ String viewLabel = adapter.tableTypeForView();
+ if (viewLabel != null) {
+ assertTrue("Missing type for view '" + viewLabel + "' - " + tableTypes, tableTypes.contains(viewLabel));
+ }
+ }
+
+ @Test
+ public void testGetTables() throws Exception {
+
+ String tableLabel = adapter.tableTypeForTable();
+
+ List<DetectedDbEntity> tables = loader.createTableLoader(null, null, TableFilter.everything())
+ .getDbEntities(TableFilter.everything(), new String[]{tableLabel});
+
+ assertNotNull(tables);
+
+ boolean foundArtist = false;
+
+ for (DetectedDbEntity table : tables) {
+ if ("ARTIST".equalsIgnoreCase(table.getName())) {
+ foundArtist = true;
+ break;
+ }
+ }
+
+ assertTrue("'ARTIST' is missing from the table list: " + tables, foundArtist);
+ }
+
+ @Test
+ public void testGetTablesWithWrongCatalog() throws Exception {
+
+ DbLoaderConfiguration config = new DbLoaderConfiguration();
+ config.setFiltersConfig(
+ FiltersConfig.create("WRONG", null, TableFilter.everything(), PatternFilter.INCLUDE_NOTHING));
+ List<DetectedDbEntity> tables = loader
+ .createTableLoader("WRONG", null, TableFilter.everything())
+ .getDbEntities(TableFilter.everything(), new String[]{adapter.tableTypeForTable()});
+
+ assertNotNull(tables);
+ assertTrue(tables.isEmpty());
+ }
+
+ @Test
+ public void testGetTablesWithWrongSchema() throws Exception {
+
+ DbLoaderConfiguration config = new DbLoaderConfiguration();
+ config.setFiltersConfig(
+ FiltersConfig.create(null, "WRONG", TableFilter.everything(), PatternFilter.INCLUDE_NOTHING));
+ List<DetectedDbEntity> tables = loader
+ .createTableLoader(null, "WRONG", TableFilter.everything())
+ .getDbEntities(TableFilter.everything(), new String[]{adapter.tableTypeForTable()});
+
+ assertNotNull(tables);
+ assertTrue(tables.isEmpty());
+ }
+
+ @Test
+ public void testLoadWithMeaningfulPK() throws Exception {
+
+ DataMap map = new DataMap();
+ String[] tableLabel = { adapter.tableTypeForTable() };
+
+ loader.setCreatingMeaningfulPK(true);
+
+ List<DbEntity> entities = loader
+ .createTableLoader(null, null, TableFilter.everything())
+ .loadDbEntities(map, CONFIG, tableLabel);
+
+ loader.loadObjEntities(map, CONFIG, entities);
+
+ ObjEntity artist = map.getObjEntity("Artist");
+ assertNotNull(artist);
+
+ ObjAttribute id = artist.getAttribute("artistId");
+ assertNotNull(id);
+ }
+
+ /**
+ * DataMap loading is in one big test method, since breaking it in
+ * individual tests would require multiple reads of metatdata which is
+ * extremely slow on some RDBMS (Sybase).
+ */
+ @Test
+ public void testLoad() throws Exception {
+
+ boolean supportsUnique = runtime.getDataDomain().getDataNodes().iterator().next().getAdapter()
+ .supportsUniqueConstraints();
+ boolean supportsLobs = accessStackAdapter.supportsLobs();
+ boolean supportsFK = accessStackAdapter.supportsFKConstraints();
+
+ DataMap map = new DataMap();
+ map.setDefaultPackage("foo.x");
+
+ String tableLabel = adapter.tableTypeForTable();
+
+ // *** TESTING THIS ***
+ List<DbEntity> entities = loader
+ .createTableLoader(null, null, TableFilter.everything())
+ .loadDbEntities(map, CONFIG, new String[]{adapter.tableTypeForTable()});
+
+
+ assertDbEntities(map);
+
+ if (supportsLobs) {
+ assertLobDbEntities(map);
+ }
+
+ // *** TESTING THIS ***
+ loader.loadDbRelationships(CONFIG, null, null, entities);
+
+ if (supportsFK) {
+ Collection<DbRelationship> rels = getDbEntity(map, "ARTIST").getRelationships();
+ assertNotNull(rels);
+ assertTrue(!rels.isEmpty());
+
+ // test one-to-one
+ rels = getDbEntity(map, "PAINTING").getRelationships();
+ assertNotNull(rels);
+
+ // find relationship to PAINTING_INFO
+ DbRelationship oneToOne = null;
+ for (DbRelationship rel : rels) {
+ if ("PAINTING_INFO".equalsIgnoreCase(rel.getTargetEntityName())) {
+ oneToOne = rel;
+ break;
+ }
+ }
+
+ assertNotNull("No relationship to PAINTING_INFO", oneToOne);
+ assertFalse("Relationship to PAINTING_INFO must be to-one", oneToOne.isToMany());
+ assertTrue("Relationship to PAINTING_INFO must be to-one", oneToOne.isToDependentPK());
+
+ // test UNIQUE only if FK is supported...
+ if (supportsUnique) {
+ assertUniqueConstraintsInRelationships(map);
+ }
+ }
+
+ // *** TESTING THIS ***
+ loader.setCreatingMeaningfulPK(false);
+ loader.loadObjEntities(map, CONFIG, entities);
+
+ assertObjEntities(map);
+
+ // now when the map is loaded, test
+ // various things
+ // selectively check how different types were processed
+ if (accessStackAdapter.supportsColumnTypeReengineering()) {
+ checkTypes(map);
+ }
+ }
+
+ private void assertUniqueConstraintsInRelationships(DataMap map) {
+ // unfortunately JDBC metadata doesn't provide info for UNIQUE
+ // constraints....
+ // cant reengineer them...
+
+ // find rel to TO_ONEFK1
+ /*
+ * Iterator it = getDbEntity(map,
+ * "TO_ONEFK2").getRelationships().iterator(); DbRelationship rel =
+ * (DbRelationship) it.next(); assertEquals("TO_ONEFK1",
+ * rel.getTargetEntityName());
+ * assertFalse("UNIQUE constraint was ignored...", rel.isToMany());
+ */
+ }
+
+ private void assertDbEntities(DataMap map) {
+ DbEntity dae = getDbEntity(map, "ARTIST");
+ assertNotNull("Null 'ARTIST' entity, other DbEntities: " + map.getDbEntityMap(), dae);
+ assertEquals("ARTIST", dae.getName().toUpperCase());
+
+ DbAttribute a = getDbAttribute(dae, "ARTIST_ID");
+ assertNotNull(a);
+ assertTrue(a.isPrimaryKey());
+ assertFalse(a.isGenerated());
+
+ if (adapter.supportsGeneratedKeys()) {
+ DbEntity bag = getDbEntity(map, "GENERATED_COLUMN_TEST");
+ DbAttribute id = getDbAttribute(bag, "GENERATED_COLUMN");
+ assertTrue(id.isPrimaryKey());
+ assertTrue(id.isGenerated());
+ }
+ }
+
+ private void assertObjEntities(DataMap map) {
+
+ boolean supportsLobs = accessStackAdapter.supportsLobs();
+ boolean supportsFK = accessStackAdapter.supportsFKConstraints();
+
+ ObjEntity ae = map.getObjEntity("Artist");
+ assertNotNull(ae);
+ assertEquals("Artist", ae.getName());
+
+ // assert primary key is not an attribute
+ assertNull(ae.getAttribute("artistId"));
+
+ if (supportsLobs) {
+ assertLobObjEntities(map);
+ }
+
+ if (supportsFK) {
+ Collection<?> rels1 = ae.getRelationships();
+ assertNotNull(rels1);
+ assertTrue(rels1.size() > 0);
+ }
+
+ assertEquals("foo.x.Artist", ae.getClassName());
+ }
+
+ private void assertLobDbEntities(DataMap map) {
+ DbEntity blobEnt = getDbEntity(map, "BLOB_TEST");
+ assertNotNull(blobEnt);
+ DbAttribute blobAttr = getDbAttribute(blobEnt, "BLOB_COL");
+ assertNotNull(blobAttr);
+ assertTrue(msgForTypeMismatch(Types.BLOB, blobAttr), Types.BLOB == blobAttr.getType()
+ || Types.LONGVARBINARY == blobAttr.getType());
+
+ DbEntity clobEnt = getDbEntity(map, "CLOB_TEST");
+ assertNotNull(clobEnt);
+ DbAttribute clobAttr = getDbAttribute(clobEnt, "CLOB_COL");
+ assertNotNull(clobAttr);
+ assertTrue(msgForTypeMismatch(Types.CLOB, clobAttr), Types.CLOB == clobAttr.getType()
+ || Types.LONGVARCHAR == clobAttr.getType());
+
+/*
+ DbEntity nclobEnt = getDbEntity(map, "NCLOB_TEST");
+ assertNotNull(nclobEnt);
+ DbAttribute nclobAttr = getDbAttribute(nclobEnt, "NCLOB_COL");
+ assertNotNull(nclobAttr);
+ assertTrue(msgForTypeMismatch(Types.NCLOB, nclobAttr), Types.NCLOB == nclobAttr.getType()
+ || Types.LONGVARCHAR == nclobAttr.getType());
+*/
+ }
+
+ private void assertLobObjEntities(DataMap map) {
+ ObjEntity blobEnt = map.getObjEntity("BlobTest");
+ assertNotNull(blobEnt);
+ // BLOBs should be mapped as byte[]
+ ObjAttribute blobAttr = blobEnt.getAttribute("blobCol");
+ assertNotNull("BlobTest.blobCol failed to doLoad", blobAttr);
+ assertEquals("byte[]", blobAttr.getType());
+
+
+ ObjEntity clobEnt = map.getObjEntity("ClobTest");
+ assertNotNull(clobEnt);
+ // CLOBs should be mapped as Strings by default
+ ObjAttribute clobAttr = clobEnt.getAttribute("clobCol");
+ assertNotNull(clobAttr);
+ assertEquals(String.class.getName(), clobAttr.getType());
+
+
+ ObjEntity nclobEnt = map.getObjEntity("NclobTest");
+ assertNotNull(nclobEnt);
+ // CLOBs should be mapped as Strings by default
+ ObjAttribute nclobAttr = nclobEnt.getAttribute("nclobCol");
+ assertNotNull(nclobAttr);
+ assertEquals(String.class.getName(), nclobAttr.getType());
+ }
+
+ private DbEntity getDbEntity(DataMap map, String name) {
+ DbEntity de = map.getDbEntity(name);
+ // sometimes table names get converted to lowercase
+ if (de == null) {
+ de = map.getDbEntity(name.toLowerCase());
+ }
+
+ return de;
+ }
+
+ private DbAttribute getDbAttribute(DbEntity ent, String name) {
+ DbAttribute da = ent.getAttribute(name);
+ // sometimes table names get converted to lowercase
+ if (da == null) {
+ da = ent.getAttribute(name.toLowerCase());
+ }
+
+ return da;
+ }
+
+ private DataMap originalMap() {
+ return runtime.getDataDomain().getDataNodes().iterator().next().getDataMaps().iterator().next();
+ }
+
+ /**
+ * Selectively check how different types were processed.
+ */
+ public void checkTypes(DataMap map) {
+ DbEntity dbe = getDbEntity(map, "PAINTING");
+ DbEntity floatTest = getDbEntity(map, "FLOAT_TEST");
+ DbEntity smallintTest = getDbEntity(map, "SMALLINT_TEST");
+ DbAttribute integerAttr = getDbAttribute(dbe, "PAINTING_ID");
+ DbAttribute decimalAttr = getDbAttribute(dbe, "ESTIMATED_PRICE");
+ DbAttribute varcharAttr = getDbAttribute(dbe, "PAINTING_TITLE");
+ DbAttribute floatAttr = getDbAttribute(floatTest, "FLOAT_COL");
+ DbAttribute smallintAttr = getDbAttribute(smallintTest, "SMALLINT_COL");
+
+ // check decimal
+ assertTrue(msgForTypeMismatch(Types.DECIMAL, decimalAttr), Types.DECIMAL == decimalAttr.getType()
+ || Types.NUMERIC == decimalAttr.getType());
+ assertEquals(2, decimalAttr.getScale());
+
+ // check varchar
+ assertEquals(msgForTypeMismatch(Types.VARCHAR, varcharAttr), Types.VARCHAR, varcharAttr.getType());
+ assertEquals(255, varcharAttr.getMaxLength());
+ // check integer
+ assertEquals(msgForTypeMismatch(Types.INTEGER, integerAttr), Types.INTEGER, integerAttr.getType());
+ // check float
+ assertTrue(msgForTypeMismatch(Types.FLOAT, floatAttr), Types.FLOAT == floatAttr.getType()
+ || Types.DOUBLE == floatAttr.getType() || Types.REAL == floatAttr.getType());
+
+ // check smallint
+ assertTrue(msgForTypeMismatch(Types.SMALLINT, smallintAttr), Types.SMALLINT == smallintAttr.getType()
+ || Types.INTEGER == smallintAttr.getType());
+ }
+
+ public void checkAllDBEntities(DataMap map) {
+
+ for (DbEntity origEnt : originalMap().getDbEntities()) {
+ DbEntity newEnt = map.getDbEntity(origEnt.getName());
+ for (DbAttribute origAttr : origEnt.getAttributes()) {
+ DbAttribute newAttr = newEnt.getAttribute(origAttr.getName());
+ assertNotNull("No matching DbAttribute for '" + origAttr.getName(), newAttr);
+ assertEquals(msgForTypeMismatch(origAttr, newAttr), origAttr.getType(), newAttr.getType());
+ // length and precision doesn't have to be the same
+ // it must be greater or equal
+ assertTrue(origAttr.getMaxLength() <= newAttr.getMaxLength());
+ assertTrue(origAttr.getScale() <= newAttr.getScale());
+ }
+ }
+ }
+
+ private static String msgForTypeMismatch(DbAttribute origAttr, DbAttribute newAttr) {
+ return msgForTypeMismatch(origAttr.getType(), newAttr);
+ }
+
+ private static String msgForTypeMismatch(int origType, DbAttribute newAttr) {
+ String nt = TypesMapping.getSqlNameByType(newAttr.getType());
+ String ot = TypesMapping.getSqlNameByType(origType);
+ return attrMismatch(newAttr.getName(), "expected type: <" + ot + ">, but was <" + nt + ">");
+ }
+
+ private static String attrMismatch(String attrName, String msg) {
+ return "[Error loading attribute '" + attrName + "': " + msg + "]";
+ }
+}