This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit b73dc18206fb11494e322abd07f167572519809b Author: Andrus Adamchik <[email protected]> AuthorDate: Thu May 14 10:40:24 2026 -0400 tests cleanup - unwinding DI --- .../org/apache/cayenne/test/jdbc/DBHelper.java | 20 +- .../org/apache/cayenne/test/jdbc/SQLBuilder.java | 22 +- .../apache/cayenne/test/jdbc/UpdateBuilder.java | 2 +- .../org/apache/cayenne/test/jdbc/WhereBuilder.java | 4 +- .../org/apache/cayenne/dba/JdbcPkGeneratorIT.java | 4 +- .../unit/di/runtime/AllTestsSchemaManager.java | 403 ++++++++++++++++++++ .../cayenne/unit/di/runtime/CayenneTestsEnv.java | 8 +- .../apache/cayenne/unit/di/runtime/DBCleaner.java | 4 +- .../cayenne/unit/di/runtime/FlavoredDBHelper.java | 8 +- .../cayenne/unit/di/runtime/RuntimeCaseModule.java | 2 +- .../cayenne/unit/di/runtime/SchemaBuilder.java | 409 --------------------- 11 files changed, 441 insertions(+), 445 deletions(-) diff --git a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/DBHelper.java b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/DBHelper.java index 01cd10325..4ad7cce93 100644 --- a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/DBHelper.java +++ b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/DBHelper.java @@ -35,7 +35,7 @@ import java.util.List; */ public class DBHelper { - protected DataSource dataSource; + protected final DataSource dataSource; public DBHelper(DataSource dataSource) { this.dataSource = dataSource; @@ -115,7 +115,7 @@ public class DBHelper { /** * Inserts a single row. Columns types can be null and will be determined - * from ParameterMetaData in this case. The later scenario will not work if + * from ParameterMetaData in this case. The latter scenario will not work if * values contains nulls and the DB is Oracle. */ public void insert(String table, String[] columns, Object[] values, int[] columnTypes) throws SQLException { @@ -136,9 +136,7 @@ public class DBHelper { } sql.append(") VALUES (?"); - for (int i = 1; i < values.length; i++) { - sql.append(", ?"); - } + sql.append(", ?".repeat(values.length - 1)); sql.append(")"); try (Connection c = getConnection()) { @@ -224,14 +222,14 @@ public class DBHelper { public Object getObject(String table, String column) throws SQLException { final String sql = "select " + quote(column) + " from " + quote(table); - return new RowTemplate<Object>(this) { + return new RowTemplate<>(this) { - @Override - Object readRow(ResultSet rs, String sql) throws SQLException { - return rs.getObject(1); - } + @Override + Object readRow(ResultSet rs, String sql) throws SQLException { + return rs.getObject(1); + } - }.execute(sql); + }.execute(sql); } public byte getByte(String table, String column) throws SQLException { diff --git a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/SQLBuilder.java b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/SQLBuilder.java index 141470cb3..3f5fa208e 100644 --- a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/SQLBuilder.java +++ b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/SQLBuilder.java @@ -26,21 +26,25 @@ public abstract class SQLBuilder { static final int NO_TYPE = Integer.MIN_VALUE; - protected DBHelper dbHelper; - protected Collection<Object> bindings; - protected Collection<Integer> bindingTypes; - protected StringBuilder sqlBuffer; + protected final DBHelper dbHelper; + protected final Collection<Object> bindings; + protected final Collection<Integer> bindingTypes; + protected final StringBuilder sqlBuffer; protected SQLBuilder(DBHelper dbHelper) { this( dbHelper, new StringBuilder(), - new ArrayList<Object>(), - new ArrayList<Integer>()); + new ArrayList<>(), + new ArrayList<>()); } - protected SQLBuilder(DBHelper dbHelper, StringBuilder sqlBuffer, - Collection<Object> bindings, Collection<Integer> bindingTypes) { + protected SQLBuilder( + DBHelper dbHelper, + StringBuilder sqlBuffer, + Collection<Object> bindings, + Collection<Integer> bindingTypes) { + this.dbHelper = dbHelper; this.bindings = bindings; this.bindingTypes = bindingTypes; @@ -54,7 +58,7 @@ public abstract class SQLBuilder { bindingTypes); } - protected void initBinding(Object value, int type) { + protected void bind(Object value, int type) { bindings.add(value); bindingTypes.add(type); } diff --git a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/UpdateBuilder.java b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/UpdateBuilder.java index 958f8f138..e560639d8 100644 --- a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/UpdateBuilder.java +++ b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/UpdateBuilder.java @@ -37,7 +37,7 @@ public class UpdateBuilder extends SQLBuilder { } sqlBuffer.append(dbHelper.quote(column)).append(" = ?"); - initBinding(value, valueType); + bind(value, valueType); return this; } diff --git a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/WhereBuilder.java b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/WhereBuilder.java index 18ba45439..e010b0ca0 100644 --- a/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/WhereBuilder.java +++ b/build-tools/cayenne-test-utilities/src/main/java/org/apache/cayenne/test/jdbc/WhereBuilder.java @@ -41,7 +41,7 @@ public class WhereBuilder extends SQLBuilder { } sqlBuffer.append(dbHelper.quote(column)).append(" = ?"); - initBinding(value, valueType); + bind(value, valueType); return this; } @@ -56,7 +56,7 @@ public class WhereBuilder extends SQLBuilder { } sqlBuffer.append(dbHelper.quote(column)).append(" = ?"); - initBinding(value, valueType); + bind(value, valueType); return this; } diff --git a/cayenne/src/test/java/org/apache/cayenne/dba/JdbcPkGeneratorIT.java b/cayenne/src/test/java/org/apache/cayenne/dba/JdbcPkGeneratorIT.java index 11e86e9b5..78f23afd2 100644 --- a/cayenne/src/test/java/org/apache/cayenne/dba/JdbcPkGeneratorIT.java +++ b/cayenne/src/test/java/org/apache/cayenne/dba/JdbcPkGeneratorIT.java @@ -25,7 +25,7 @@ import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.testdo.testmap.Artist; import org.apache.cayenne.unit.di.runtime.CayenneProjects; import org.apache.cayenne.unit.di.runtime.CayenneTestsEnv; -import org.apache.cayenne.unit.di.runtime.SchemaBuilder; +import org.apache.cayenne.unit.di.runtime.AllTestsSchemaManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -42,7 +42,7 @@ public class JdbcPkGeneratorIT { private DbAdapter adapter; private DataNode node; - private SchemaBuilder schemaBuilder; + private AllTestsSchemaManager schemaBuilder; @BeforeEach public void setUp() throws Exception { diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/AllTestsSchemaManager.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/AllTestsSchemaManager.java new file mode 100644 index 000000000..c1b8d8191 --- /dev/null +++ b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/AllTestsSchemaManager.java @@ -0,0 +1,403 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.unit.di.runtime; + +import org.apache.cayenne.access.DataDomain; +import org.apache.cayenne.access.DataNode; +import org.apache.cayenne.access.DbGenerator; +import org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy; +import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory; +import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory; +import org.apache.cayenne.access.translator.select.DefaultSelectTranslatorFactory; +import org.apache.cayenne.ashwood.AshwoodEntitySorter; +import org.apache.cayenne.cache.MapQueryCache; +import org.apache.cayenne.configuration.DataMapLoader; +import org.apache.cayenne.dba.DbAdapter; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.event.DefaultEventManager; +import org.apache.cayenne.log.JdbcEventLogger; +import org.apache.cayenne.map.DataMap; +import org.apache.cayenne.map.DbAttribute; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.Procedure; +import org.apache.cayenne.resource.URLResource; +import org.apache.cayenne.testdo.extended_type.StringET1ExtendedType; +import org.apache.cayenne.unit.UnitDbAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URL; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +public class AllTestsSchemaManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(AllTestsSchemaManager.class); + + private static final String SKIP_SCHEMA_PROPERTY = "cayenneTestSkipSchemaCreation"; + + private static final String[] MAPS_FOR_SCHEMA_SETUP = {"testmap.map.xml", "compound.map.xml", + "misc-types.map.xml", "things.map.xml", "numeric-types.map.xml", "binary-pk.map.xml", "no-pk.map.xml", + "lob.map.xml", "date-time.map.xml", "enum.map.xml", "json.map.xml", "extended-type.map.xml", + "generated.map.xml", "mixed-persistence-strategy.map.xml", "people.map.xml", "primitive.map.xml", + "inheritance.map.xml", "locking.map.xml", "soft-delete.map.xml", "empty.map.xml", "relationships.map.xml", + "relationships-activity.map.xml", "relationships-delete-rules.map.xml", + "relationships-collection-to-many.map.xml", "relationships-child-master.map.xml", + "relationships-clob.map.xml", "relationships-flattened.map.xml", "relationships-many-to-many-join.map.xml", + "relationships-set-to-many.map.xml", "relationships-to-many-fk.map.xml", "relationships-to-one-fk.map.xml", + "return-types.map.xml", "uuid.map.xml", "multi-tier.map.xml", "reflexive.map.xml", "delete-rules.map.xml", + "lifecycle-callbacks-order.map.xml", "lifecycles.map.xml", "map-to-many.map.xml", "toone.map.xml", + "meaningful-pk.map.xml", "table-primitives.map.xml", "generic.map.xml", "map-db1.map.xml", + "map-db2.map.xml", "embeddable.map.xml", "qualified.map.xml", "quoted-identifiers.map.xml", + "inheritance-single-table1.map.xml", "inheritance-vertical.map.xml", "oneway-rels.map.xml", + "unsupported-distinct-types.map.xml", "array-type.map.xml", "cay-2032.map.xml", + "weighted-sort.map.xml", "hybrid-data-object.map.xml", "legacy-date-time.map.xml", "inheritance-with-enum.map.xml", + "lazy-attributes.map.xml", "cay2666/datamap.map.xml", "cay2641/datamapLazy.map.xml", + "annotation/datamapAnnotation.map.xml"}; + + // hardcoded dependent entities that should be excluded if LOBs are not supported + private static final Set<String> EXTRA_EXCLUDED_FOR_NO_LOB = Set.of("CLOB_DETAIL"); + + private static final Set<String> EXTRA_EXCLUDED_FOR_NO_NATIVE_JSON = Set.of("JSON_OTHER"); + + private final RuntimeCaseDataSourceFactory dataSourceFactory; + private final UnitDbAdapter unitDbAdapter; + private final DbAdapter dbAdapter; + private final JdbcEventLogger jdbcEventLogger; + private final DataMapLoader loader; + + private DataDomain domain; + + public AllTestsSchemaManager(@Inject RuntimeCaseDataSourceFactory dataSourceFactory, @Inject UnitDbAdapter unitDbAdapter, + @Inject DbAdapter dbAdapter, @Inject JdbcEventLogger jdbcEventLogger, @Inject DataMapLoader loader) { + this.dataSourceFactory = dataSourceFactory; + this.unitDbAdapter = unitDbAdapter; + this.dbAdapter = dbAdapter; + this.jdbcEventLogger = jdbcEventLogger; + this.loader = loader; + } + + /** + * Completely rebuilds test schema. + */ + // TODO - this method changes the internal state of the object ... refactor + public void rebuildSchema() { + + // generate schema combining all DataMaps that require schema support. + // Schema generation is done like that instead of per DataMap on demand + // to avoid conflicts when dropping and generating PK objects. + + DataMap[] maps = new DataMap[MAPS_FOR_SCHEMA_SETUP.length]; + + for (int i = 0; i < maps.length; i++) { + URL mapURL = getClass().getClassLoader().getResource(MAPS_FOR_SCHEMA_SETUP[i]); + maps[i] = loader.load(new URLResource(mapURL)); + } + + this.domain = new DataDomain("temp"); + domain.setEventManager(new DefaultEventManager(2)); + domain.setEntitySorter(new AshwoodEntitySorter()); + domain.setQueryCache(new MapQueryCache(50)); + + try { + for (DataMap map : maps) { + initNode(map); + } + + if ("true".equalsIgnoreCase(System.getProperty(SKIP_SCHEMA_PROPERTY))) { + LOGGER.info("skipping schema generation... "); + } else { + dropSchema(); + dropPKSupport(); + createSchema(); + createPKSupport(); + } + } catch (Exception e) { + throw new RuntimeException("Error rebuilding schema", e); + } + } + + private void initNode(DataMap map) { + + DataNode node = new DataNode(map.getName()); + node.setJdbcEventLogger(jdbcEventLogger); + node.setAdapter(dbAdapter); + node.setDataSource(dataSourceFactory.getSharedDataSource()); + + // setup test extended types + node.getAdapter().getExtendedTypes().registerType(new StringET1ExtendedType()); + + // tweak mapping with a delegate + for (Procedure proc : map.getProcedures()) { + unitDbAdapter.tweakProcedure(proc); + } + filterDataMap(map); + + node.addDataMap(map); + + node.setSchemaUpdateStrategy(new SkipSchemaUpdateStrategy()); + node.setRowReaderFactory(new DefaultRowReaderFactory()); + node.setBatchTranslatorFactory(new DefaultBatchTranslatorFactory()); + node.setSelectTranslatorFactory(new DefaultSelectTranslatorFactory()); + domain.addNode(node); + } + + private void filterDataMap(DataMap map) { + boolean supportsBinaryPK = unitDbAdapter.supportsBinaryPK(); + + if (supportsBinaryPK) { + return; + } + + List<DbEntity> entitiesToRemove = new ArrayList<>(); + + 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); + } + } + + /** + * Drops all test tables. + */ + private void dropSchema() throws Exception { + for (DataNode node : domain.getDataNodes()) { + dropSchema(node, node.getDataMaps().iterator().next()); + } + } + + /** + * Creates all test tables in the database. + */ + private void createSchema() throws Exception { + for (DataNode node : domain.getDataNodes()) { + createSchema(node, node.getDataMaps().iterator().next()); + } + } + + public void dropPKSupport() throws Exception { + for (DataNode node : domain.getDataNodes()) { + dropPKSupport(node, node.getDataMaps().iterator().next()); + } + } + + /** + * Creates primary key support for all node DbEntities. Will use its + * facilities provided by DbAdapter to generate any necessary database + * objects and data for primary key support. + */ + public void createPKSupport() throws Exception { + for (DataNode node : domain.getDataNodes()) { + createPKSupport(node, node.getDataMaps().iterator().next()); + } + } + + /** + * Helper method that orders DbEntities to satisfy referential constraints + * and returns an ordered list. + */ + private List<DbEntity> dbEntitiesInInsertOrder(DataMap map) { + DataMap localMap = domain.getDataMap(map.getName()); + List<DbEntity> entities = new ArrayList<>(localMap.getDbEntities()); + entities.removeAll(excludeEntities(entities)); + + domain.getEntitySorter().sortDbEntities(entities, false); + return entities; + } + + public List<DbEntity> dbEntitiesInDeleteOrder(DataMap map) { + return dbEntitiesInInsertOrder(map).reversed(); + } + + private List<DbEntity> excludeEntities(Collection<DbEntity> entities) { + // exclude various unsupported tests... + + boolean excludeLOB = !unitDbAdapter.supportsLobs(); + boolean excludeNativeJson = !unitDbAdapter.supportsJsonType(); + boolean excludeBinaryPK = !unitDbAdapter.supportsBinaryPK(); + if (!excludeLOB && !excludeNativeJson && !excludeBinaryPK) { + return Collections.emptyList(); + } + + List<DbEntity> excludedEntities = new ArrayList<>(); + for (DbEntity entity : entities) { + + // check for LOB attributes + if (excludeLOB) { + if (EXTRA_EXCLUDED_FOR_NO_LOB.contains(entity.getName())) { + excludedEntities.add(entity); + continue; + } + Set<Integer> lobTypes = Set.of(Types.BLOB, Types.CLOB, Types.NCLOB); + boolean hasLob = entity.getAttributes().stream() + .map(DbAttribute::getType) + .anyMatch(lobTypes::contains); + if (hasLob) { + excludedEntities.add(entity); + continue; + } + } + + // check for native json type + if (excludeNativeJson) { + if (EXTRA_EXCLUDED_FOR_NO_NATIVE_JSON.contains(entity.getName())) { + excludedEntities.add(entity); + continue; + } + } + + // check for BIN PK + if (excludeBinaryPK) { + Set<Integer> binaryTypes = Set.of(Types.BINARY, Types.VARBINARY, Types.LONGVARBINARY); + boolean hasBinaryPK = entity.getAttributes().stream() + .filter(attribute -> attribute.isPrimaryKey() || attribute.isForeignKey()) + .map(DbAttribute::getType) + .anyMatch(binaryTypes::contains); + if (hasBinaryPK) { + excludedEntities.add(entity); + } + } + } + return excludedEntities; + } + + private void dropSchema(DataNode node, DataMap map) throws Exception { + + List<DbEntity> list = dbEntitiesInInsertOrder(map); + + try (Connection conn = dataSourceFactory.getSharedDataSource().getConnection()) { + + DatabaseMetaData md = conn.getMetaData(); + List<String> allTables = new ArrayList<>(); + + try (ResultSet tables = md.getTables(null, null, "%", null)) { + while (tables.next()) { + // 'toUpperCase' is needed since most databases are case insensitive, + // and some will convert names to lower case (e.g. PostgreSQL) + String name = tables.getString("TABLE_NAME"); + if (name != null) { + allTables.add(name.toUpperCase()); + } + } + } + + unitDbAdapter.willDropTables(conn, map, allTables); + + // drop all tables in the map + try (Statement stmt = conn.createStatement()) { + + ListIterator<DbEntity> it = list.listIterator(list.size()); + while (it.hasPrevious()) { + DbEntity ent = it.previous(); + if (!allTables.contains(ent.getName().toUpperCase())) { + continue; + } + + for (String dropSql : node.getAdapter().dropTableStatements(ent)) { + try { + LOGGER.info(dropSql); + stmt.execute(dropSql); + } catch (SQLException sqe) { + LOGGER.warn("Can't drop table " + ent.getName() + ", ignoring...", sqe); + } + } + } + } + + unitDbAdapter.droppedTables(conn, map); + } + } + + private void dropPKSupport(DataNode node, DataMap map) throws Exception { + List<DbEntity> filteredEntities = dbEntitiesInInsertOrder(map); + node.getAdapter().getPkGenerator().dropAutoPk(node, filteredEntities); + } + + private void createPKSupport(DataNode node, DataMap map) throws Exception { + List<DbEntity> filteredEntities = dbEntitiesInInsertOrder(map); + node.getAdapter().getPkGenerator().createAutoPk(node, filteredEntities); + } + + private void createSchema(DataNode node, DataMap map) throws Exception { + + try (Connection conn = dataSourceFactory.getSharedDataSource().getConnection()) { + unitDbAdapter.willCreateTables(conn, map); + try (Statement stmt = conn.createStatement()) { + + for (String query : tableCreateQueries(node, map)) { + LOGGER.info(query); + stmt.execute(query); + } + } + unitDbAdapter.createdTables(conn, map); + } + } + + /** + * Returns iterator of preprocessed table create queries. + */ + private Collection<String> tableCreateQueries(DataNode node, DataMap map) { + DbAdapter adapter = node.getAdapter(); + + List<DbEntity> orderedEntities = dbEntitiesInInsertOrder(map); + List<DbEntity> excludedEntities = excludeEntities(map.getDbEntities()); + DbGenerator gen = new DbGenerator(adapter, map, excludedEntities, domain, jdbcEventLogger); + List<String> queries = new ArrayList<>(); + + // table definitions + for (DbEntity ent : orderedEntities) { + queries.add(adapter.createTable(ent)); + } + + // FK constraints + for (DbEntity ent : orderedEntities) { + if (!unitDbAdapter.supportsFKConstraints(ent)) { + continue; + } + + List<String> qs = gen.createConstraintsQueries(ent); + queries.addAll(qs); + } + + return queries; + } +} diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneTestsEnv.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneTestsEnv.java index 5c0d11734..482dd7427 100644 --- a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneTestsEnv.java +++ b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneTestsEnv.java @@ -53,7 +53,7 @@ public class CayenneTestsEnv implements BeforeEachCallback, AfterEachCallback { static { TEST_SCOPE = new DefaultScope(); INJECTOR = DIBootstrap.createInjector(new RuntimeCaseModule(TEST_SCOPE)); - INJECTOR.getInstance(SchemaBuilder.class).rebuildSchema(); + INJECTOR.getInstance(AllTestsSchemaManager.class).rebuildSchema(); } private final String project; @@ -119,7 +119,7 @@ public class CayenneTestsEnv implements BeforeEachCallback, AfterEachCallback { this.dbHelper = INJECTOR.getInstance(DBHelper.class); this.dbCleaner = new DBCleaner( (FlavoredDBHelper) dbHelper, - INJECTOR.getInstance(SchemaBuilder.class), + INJECTOR.getInstance(AllTestsSchemaManager.class), runtime.getDataDomain().getDataMaps()); if (autoClean) { @@ -197,8 +197,8 @@ public class CayenneTestsEnv implements BeforeEachCallback, AfterEachCallback { return INJECTOR.getInstance(SQLTemplateCustomizer.class); } - public SchemaBuilder schemaBuilder() { - return INJECTOR.getInstance(SchemaBuilder.class); + public AllTestsSchemaManager schemaBuilder() { + return INJECTOR.getInstance(AllTestsSchemaManager.class); } public static class WeakReferenceStrategyModule implements Module { diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/DBCleaner.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/DBCleaner.java index 03e3685ef..d5150fd5a 100644 --- a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/DBCleaner.java +++ b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/DBCleaner.java @@ -29,10 +29,10 @@ import java.util.List; public class DBCleaner { private final FlavoredDBHelper dbHelper; - private final SchemaBuilder schemaBuilder; + private final AllTestsSchemaManager schemaBuilder; private final Collection<DataMap> dataMaps; - public DBCleaner(FlavoredDBHelper dbHelper, SchemaBuilder schemaBuilder, Collection<DataMap> dataMaps) { + public DBCleaner(FlavoredDBHelper dbHelper, AllTestsSchemaManager schemaBuilder, Collection<DataMap> dataMaps) { this.dbHelper = dbHelper; this.schemaBuilder = schemaBuilder; this.dataMaps = dataMaps; diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/FlavoredDBHelper.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/FlavoredDBHelper.java index 7b2108a77..2039f3e27 100644 --- a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/FlavoredDBHelper.java +++ b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/FlavoredDBHelper.java @@ -18,19 +18,19 @@ ****************************************************************/ package org.apache.cayenne.unit.di.runtime; -import javax.sql.DataSource; - import org.apache.cayenne.dba.QuotingStrategy; import org.apache.cayenne.map.DataMap; import org.apache.cayenne.test.jdbc.DBHelper; +import javax.sql.DataSource; + /** * A DbHelper that understands various supported DB flavors. */ public class FlavoredDBHelper extends DBHelper { - private QuotingStrategy quotingStrategy; - private DataMap dataMap; + private final QuotingStrategy quotingStrategy; + private final DataMap dataMap; public FlavoredDBHelper(DataSource dataSource, QuotingStrategy quotingStrategy, DataMap dataMap) { super(dataSource); diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseModule.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseModule.java index 33c0888d7..9694badd0 100644 --- a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseModule.java +++ b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseModule.java @@ -202,7 +202,7 @@ public class RuntimeCaseModule implements Module { binder.bind(ValueObjectTypeRegistry.class).to(DefaultValueObjectTypeRegistry.class); binder.bind(ValueComparisonStrategyFactory.class).to(DefaultValueComparisonStrategyFactory.class); - binder.bind(SchemaBuilder.class).to(SchemaBuilder.class); + binder.bind(AllTestsSchemaManager.class).to(AllTestsSchemaManager.class); binder.bind(JdbcEventLogger.class).to(Slf4jJdbcEventLogger.class); binder.bind(RuntimeProperties.class).to(DefaultRuntimeProperties.class); binder.bind(ObjectMapRetainStrategy.class).to(DefaultObjectMapRetainStrategy.class); diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/SchemaBuilder.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/SchemaBuilder.java deleted file mode 100644 index 1c37a7f44..000000000 --- a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/SchemaBuilder.java +++ /dev/null @@ -1,409 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ - -package org.apache.cayenne.unit.di.runtime; - -import org.apache.cayenne.access.DataDomain; -import org.apache.cayenne.access.DataNode; -import org.apache.cayenne.access.DbGenerator; -import org.apache.cayenne.access.dbsync.SkipSchemaUpdateStrategy; -import org.apache.cayenne.access.jdbc.reader.DefaultRowReaderFactory; -import org.apache.cayenne.access.translator.batch.DefaultBatchTranslatorFactory; -import org.apache.cayenne.access.translator.select.DefaultSelectTranslatorFactory; -import org.apache.cayenne.ashwood.AshwoodEntitySorter; -import org.apache.cayenne.cache.MapQueryCache; -import org.apache.cayenne.configuration.DataMapLoader; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.di.Inject; -import org.apache.cayenne.event.DefaultEventManager; -import org.apache.cayenne.log.JdbcEventLogger; -import org.apache.cayenne.map.DataMap; -import org.apache.cayenne.map.DbAttribute; -import org.apache.cayenne.map.DbEntity; -import org.apache.cayenne.map.Procedure; -import org.apache.cayenne.resource.URLResource; -import org.apache.cayenne.testdo.extended_type.StringET1ExtendedType; -import org.apache.cayenne.unit.UnitDbAdapter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URL; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Types; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; - -/** - * Default implementation of the AccessStack that has a single DataNode per DataMap. - */ -public class SchemaBuilder { - - private static final Logger logger = LoggerFactory.getLogger(SchemaBuilder.class); - - public static final String SKIP_SCHEMA_KEY = "cayenneTestSkipSchemaCreation"; - - private static final String[] MAPS_REQUIRING_SCHEMA_SETUP = { "testmap.map.xml", "compound.map.xml", - "misc-types.map.xml", "things.map.xml", "numeric-types.map.xml", "binary-pk.map.xml", "no-pk.map.xml", - "lob.map.xml", "date-time.map.xml", "enum.map.xml", "json.map.xml", "extended-type.map.xml", - "generated.map.xml", "mixed-persistence-strategy.map.xml", "people.map.xml", "primitive.map.xml", - "inheritance.map.xml", "locking.map.xml", "soft-delete.map.xml", "empty.map.xml", "relationships.map.xml", - "relationships-activity.map.xml", "relationships-delete-rules.map.xml", - "relationships-collection-to-many.map.xml", "relationships-child-master.map.xml", - "relationships-clob.map.xml", "relationships-flattened.map.xml", "relationships-many-to-many-join.map.xml", - "relationships-set-to-many.map.xml", "relationships-to-many-fk.map.xml", "relationships-to-one-fk.map.xml", - "return-types.map.xml", "uuid.map.xml", "multi-tier.map.xml", "reflexive.map.xml", "delete-rules.map.xml", - "lifecycle-callbacks-order.map.xml", "lifecycles.map.xml", "map-to-many.map.xml", "toone.map.xml", - "meaningful-pk.map.xml", "table-primitives.map.xml", "generic.map.xml", "map-db1.map.xml", - "map-db2.map.xml", "embeddable.map.xml", "qualified.map.xml", "quoted-identifiers.map.xml", - "inheritance-single-table1.map.xml", "inheritance-vertical.map.xml", "oneway-rels.map.xml", - "unsupported-distinct-types.map.xml", "array-type.map.xml", "cay-2032.map.xml", - "weighted-sort.map.xml", "hybrid-data-object.map.xml", "legacy-date-time.map.xml", "inheritance-with-enum.map.xml", - "lazy-attributes.map.xml", "cay2666/datamap.map.xml", "cay2641/datamapLazy.map.xml", - "annotation/datamapAnnotation.map.xml" }; - - // hardcoded dependent entities that should be excluded - // if LOBs are not supported - private static final Set<String> EXTRA_EXCLUDED_FOR_NO_LOB = Set.of("CLOB_DETAIL"); - - private static final Set<String> EXTRA_EXCLUDED_FOR_NO_NATIVE_JSON = Set.of("JSON_OTHER"); - - private RuntimeCaseDataSourceFactory dataSourceFactory; - private UnitDbAdapter unitDbAdapter; - private DbAdapter dbAdapter; - private DataDomain domain; - private JdbcEventLogger jdbcEventLogger; - - @Inject - DataMapLoader loader; - - public SchemaBuilder(@Inject RuntimeCaseDataSourceFactory dataSourceFactory, @Inject UnitDbAdapter unitDbAdapter, - @Inject DbAdapter dbAdapter, @Inject JdbcEventLogger jdbcEventLogger) { - this.dataSourceFactory = dataSourceFactory; - this.unitDbAdapter = unitDbAdapter; - this.dbAdapter = dbAdapter; - this.jdbcEventLogger = jdbcEventLogger; - } - - /** - * Completely rebuilds test schema. - */ - // TODO - this method changes the internal state of the object ... refactor - public void rebuildSchema() { - - // generate schema combining all DataMaps that require schema support. - // Schema generation is done like that instead of per DataMap on demand - // to avoid conflicts when dropping and generating PK objects. - - DataMap[] maps = new DataMap[MAPS_REQUIRING_SCHEMA_SETUP.length]; - - for (int i = 0; i < maps.length; i++) { - URL mapURL = getClass().getClassLoader().getResource(MAPS_REQUIRING_SCHEMA_SETUP[i]); - maps[i] = loader.load(new URLResource(mapURL)); - } - - this.domain = new DataDomain("temp"); - domain.setEventManager(new DefaultEventManager(2)); - domain.setEntitySorter(new AshwoodEntitySorter()); - domain.setQueryCache(new MapQueryCache(50)); - - try { - for (DataMap map : maps) { - initNode(map); - } - - if ("true".equalsIgnoreCase(System.getProperty(SKIP_SCHEMA_KEY))) { - logger.info("skipping schema generation... "); - } else { - dropSchema(); - dropPKSupport(); - createSchema(); - createPKSupport(); - } - } catch (Exception e) { - throw new RuntimeException("Error rebuilding schema", e); - } - } - - private void initNode(DataMap map) { - - DataNode node = new DataNode(map.getName()); - node.setJdbcEventLogger(jdbcEventLogger); - node.setAdapter(dbAdapter); - node.setDataSource(dataSourceFactory.getSharedDataSource()); - - // setup test extended types - node.getAdapter().getExtendedTypes().registerType(new StringET1ExtendedType()); - - // tweak mapping with a delegate - for (Procedure proc : map.getProcedures()) { - unitDbAdapter.tweakProcedure(proc); - } - filterDataMap(map); - - node.addDataMap(map); - - node.setSchemaUpdateStrategy(new SkipSchemaUpdateStrategy()); - node.setRowReaderFactory(new DefaultRowReaderFactory()); - node.setBatchTranslatorFactory(new DefaultBatchTranslatorFactory()); - node.setSelectTranslatorFactory(new DefaultSelectTranslatorFactory()); - domain.addNode(node); - } - - /** - * Remote binary pk {@link DbEntity} for {@link DbAdapter} not supporting - * that and so on. - */ - protected void filterDataMap(DataMap map) { - boolean supportsBinaryPK = unitDbAdapter.supportsBinaryPK(); - - if (supportsBinaryPK) { - return; - } - - List<DbEntity> entitiesToRemove = new ArrayList<>(); - - 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); - } - } - - /** Drops all test tables. */ - private void dropSchema() throws Exception { - for (DataNode node : domain.getDataNodes()) { - dropSchema(node, node.getDataMaps().iterator().next()); - } - } - - /** - * Creates all test tables in the database. - */ - private void createSchema() throws Exception { - for (DataNode node : domain.getDataNodes()) { - createSchema(node, node.getDataMaps().iterator().next()); - } - } - - public void dropPKSupport() throws Exception { - for (DataNode node : domain.getDataNodes()) { - dropPKSupport(node, node.getDataMaps().iterator().next()); - } - } - - /** - * Creates primary key support for all node DbEntities. Will use its - * facilities provided by DbAdapter to generate any necessary database - * objects and data for primary key support. - */ - public void createPKSupport() throws Exception { - for (DataNode node : domain.getDataNodes()) { - createPKSupport(node, node.getDataMaps().iterator().next()); - } - } - - /** - * Helper method that orders DbEntities to satisfy referential constraints - * and returns an ordered list. - */ - private List<DbEntity> dbEntitiesInInsertOrder(DataMap map) { - DataMap localMap = domain.getDataMap(map.getName()); - List<DbEntity> entities = new ArrayList<>(localMap.getDbEntities()); - entities.removeAll(excludeEntities(entities)); - - domain.getEntitySorter().sortDbEntities(entities, false); - return entities; - } - - public List<DbEntity> dbEntitiesInDeleteOrder(DataMap map) { - return dbEntitiesInInsertOrder(map).reversed(); - } - - private List<DbEntity> excludeEntities(Collection<DbEntity> entities) { - // exclude various unsupported tests... - - boolean excludeLOB = !unitDbAdapter.supportsLobs(); - boolean excludeNativeJson = !unitDbAdapter.supportsJsonType(); - boolean excludeBinaryPK = !unitDbAdapter.supportsBinaryPK(); - if (!excludeLOB && !excludeNativeJson && !excludeBinaryPK) { - return Collections.emptyList(); - } - - List<DbEntity> excludedEntities = new ArrayList<>(); - for (DbEntity entity : entities) { - - // check for LOB attributes - if (excludeLOB) { - if (EXTRA_EXCLUDED_FOR_NO_LOB.contains(entity.getName())) { - excludedEntities.add(entity); - continue; - } - Set<Integer> lobTypes = Set.of(Types.BLOB, Types.CLOB, Types.NCLOB); - boolean hasLob = entity.getAttributes().stream() - .map(DbAttribute::getType) - .anyMatch(lobTypes::contains); - if (hasLob) { - excludedEntities.add(entity); - continue; - } - } - - // check for native json type - if (excludeNativeJson) { - if (EXTRA_EXCLUDED_FOR_NO_NATIVE_JSON.contains(entity.getName())) { - excludedEntities.add(entity); - continue; - } - } - - // check for BIN PK - if (excludeBinaryPK) { - Set<Integer> binaryTypes = Set.of(Types.BINARY, Types.VARBINARY, Types.LONGVARBINARY); - boolean hasBinaryPK = entity.getAttributes().stream() - .filter(attribute -> attribute.isPrimaryKey() || attribute.isForeignKey()) - .map(DbAttribute::getType) - .anyMatch(binaryTypes::contains); - if (hasBinaryPK) { - excludedEntities.add(entity); - } - } - } - return excludedEntities; - } - - private void dropSchema(DataNode node, DataMap map) throws Exception { - - List<DbEntity> list = dbEntitiesInInsertOrder(map); - - try (Connection conn = dataSourceFactory.getSharedDataSource().getConnection()) { - - DatabaseMetaData md = conn.getMetaData(); - List<String> allTables = new ArrayList<>(); - - try (ResultSet tables = md.getTables(null, null, "%", null)) { - while (tables.next()) { - // 'toUpperCase' is needed since most databases are case insensitive, - // and some will convert names to lower case (e.g. PostgreSQL) - String name = tables.getString("TABLE_NAME"); - if (name != null) { - allTables.add(name.toUpperCase()); - } - } - } - - unitDbAdapter.willDropTables(conn, map, allTables); - - // drop all tables in the map - try (Statement stmt = conn.createStatement()) { - - ListIterator<DbEntity> it = list.listIterator(list.size()); - while (it.hasPrevious()) { - DbEntity ent = it.previous(); - if (!allTables.contains(ent.getName().toUpperCase())) { - continue; - } - - for (String dropSql : node.getAdapter().dropTableStatements(ent)) { - try { - logger.info(dropSql); - stmt.execute(dropSql); - } catch (SQLException sqe) { - logger.warn("Can't drop table " + ent.getName() + ", ignoring...", sqe); - } - } - } - } - - unitDbAdapter.droppedTables(conn, map); - } - } - - private void dropPKSupport(DataNode node, DataMap map) throws Exception { - List<DbEntity> filteredEntities = dbEntitiesInInsertOrder(map); - node.getAdapter().getPkGenerator().dropAutoPk(node, filteredEntities); - } - - private void createPKSupport(DataNode node, DataMap map) throws Exception { - List<DbEntity> filteredEntities = dbEntitiesInInsertOrder(map); - node.getAdapter().getPkGenerator().createAutoPk(node, filteredEntities); - } - - private void createSchema(DataNode node, DataMap map) throws Exception { - - try (Connection conn = dataSourceFactory.getSharedDataSource().getConnection()) { - unitDbAdapter.willCreateTables(conn, map); - try (Statement stmt = conn.createStatement()) { - - for (String query : tableCreateQueries(node, map)) { - logger.info(query); - stmt.execute(query); - } - } - unitDbAdapter.createdTables(conn, map); - } - } - - /** - * Returns iterator of preprocessed table create queries. - */ - private Collection<String> tableCreateQueries(DataNode node, DataMap map) { - DbAdapter adapter = node.getAdapter(); - - List<DbEntity> orderedEntities = dbEntitiesInInsertOrder(map); - List<DbEntity> excludedEntities = excludeEntities(map.getDbEntities()); - DbGenerator gen = new DbGenerator(adapter, map, excludedEntities, domain, jdbcEventLogger); - List<String> queries = new ArrayList<>(); - - // table definitions - for (DbEntity ent : orderedEntities) { - queries.add(adapter.createTable(ent)); - } - - // FK constraints - for (DbEntity ent : orderedEntities) { - if (!unitDbAdapter.supportsFKConstraints(ent)) { - continue; - } - - List<String> qs = gen.createConstraintsQueries(ent); - queries.addAll(qs); - } - - return queries; - } -}
