diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 3451885df..1bcf68f20 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -13,7 +13,7 @@ - + Add repo-root README.adoc, rendered natively by GitHub via Asciidoctor, so the repository landing page shows a pitch, build/reproducible-build badges, a pointer to the "dbUnit in 5 Minutes" tutorial, and links to the documentation site, Maven coordinates, GitHub Discussions, and CONTRIBUTING.md instead of nothing. @@ -224,6 +224,9 @@ Fix NoSuchColumnException when two same-named tables merged by CompositeDataSet (e.g. from two separate flat-XML datasets both inserting into the same table) disagree on columns. A row belonging to a part that never declared an optional column crashed instead of being treated as not supplied, hitting InsertOperation's core insert path via equalsIgnoreMapping/getIgnoreMapping. InsertOperation.getIgnoreMapping/equalsIgnoreMapping now resolve such a column to ITable.NO_VALUE, the same sentinel already used to omit a column from a generated statement, instead of letting the row's NoSuchColumnException propagate. This is deliberately scoped to InsertOperation alone rather than CompositeTable itself: UpdateOperation and DeleteOperation bind every requested column's value directly with no equivalent ignore-mapping, so a genuinely missing column there must keep throwing instead of silently binding NULL into a WHERE clause. + + Add a DataSourceDatabaseTester(DataSource, String schema, CachingConnectionProvider, DatabaseConfig) constructor and a new DatabaseConfig#copyPropertiesInto(DatabaseConfig) method, so per-connection properties and features (e.g. PROPERTY_DATATYPE_FACTORY for a specific database) can be applied to every connection a DataSourceDatabaseTester creates directly through the constructor, instead of requiring an IOperationListener#connectionRetrieved() override to reach into each connection's DatabaseConfig after the fact. Also fix DatabaseConfig's own constructor, which left FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES's underlying property value unset despite ALL_PROPERTIES declaring it non-nullable - harmless until copyPropertiesInto()'s full iteration over ALL_PROPERTIES became the first caller to round-trip every declared property through setProperty(), which enforces that constraint strictly. + diff --git a/src/main/java/org/dbunit/DataSourceDatabaseTester.java b/src/main/java/org/dbunit/DataSourceDatabaseTester.java index 672209824..af4745e86 100644 --- a/src/main/java/org/dbunit/DataSourceDatabaseTester.java +++ b/src/main/java/org/dbunit/DataSourceDatabaseTester.java @@ -26,6 +26,7 @@ import javax.sql.DataSource; import org.dbunit.database.CachingConnectionProvider; +import org.dbunit.database.DatabaseConfig; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; @@ -47,6 +48,7 @@ public class DataSourceDatabaseTester extends AbstractDatabaseTester private static final Logger logger = LoggerFactory.getLogger(DataSourceDatabaseTester.class); private final CachingConnectionProvider connectionProvider; + private final DatabaseConfig databaseConfig; private DataSource dataSource; /** @@ -88,6 +90,28 @@ public DataSourceDatabaseTester(DataSource dataSource, String schema) */ public DataSourceDatabaseTester(DataSource dataSource, String schema, CachingConnectionProvider connectionProvider) + { + this(dataSource, schema, connectionProvider, null); + } + + /** + * Creates a new DataSourceDatabaseTester with the specified DataSource, schema name, + * optional {@link CachingConnectionProvider}, and optional {@link DatabaseConfig} whose + * property and feature values are applied to every connection this tester creates - + * for example to set {@link DatabaseConfig#PROPERTY_DATATYPE_FACTORY} for a specific + * database without needing an {@link IOperationListener}. + * + * @param dataSource The DataSource to pull connections from. + * @param schema The schema name to be used for new dbunit connections - can be null. + * @param connectionProvider Caches and validates the connection across calls - can be + * null, in which case a new connection is created on every call as before. + * @param databaseConfig The property and feature values to apply to every connection this + * tester creates - can be null, in which case each connection keeps its + * own default {@link DatabaseConfig}. + * @since 3.4.1 + */ + public DataSourceDatabaseTester(DataSource dataSource, String schema, + CachingConnectionProvider connectionProvider, DatabaseConfig databaseConfig) { super(schema); @@ -97,6 +121,7 @@ public DataSourceDatabaseTester(DataSource dataSource, String schema, } this.dataSource = dataSource; this.connectionProvider = connectionProvider; + this.databaseConfig = databaseConfig; } public IDatabaseConnection getConnection() throws Exception @@ -113,6 +138,11 @@ public IDatabaseConnection getConnection() throws Exception private IDatabaseConnection createConnection() throws Exception { - return new DatabaseConnection( dataSource.getConnection(), getSchema() ); + IDatabaseConnection connection = new DatabaseConnection( dataSource.getConnection(), getSchema() ); + if (databaseConfig != null) + { + databaseConfig.copyPropertiesInto(connection.getConfig()); + } + return connection; } } diff --git a/src/main/java/org/dbunit/database/DatabaseConfig.java b/src/main/java/org/dbunit/database/DatabaseConfig.java index a02046c17..1439a07d2 100644 --- a/src/main/java/org/dbunit/database/DatabaseConfig.java +++ b/src/main/java/org/dbunit/database/DatabaseConfig.java @@ -177,6 +177,7 @@ public DatabaseConfig() setFeature(FEATURE_QUALIFIED_TABLE_NAMES, false); setFeature(FEATURE_CASE_SENSITIVE_TABLE_NAMES, false); setFeature(FEATURE_DATATYPE_WARNING, true); + setFeature(FEATURE_SKIP_ORACLE_RECYCLEBIN_TABLES, false); setFeature(FEATURE_ALLOW_EMPTY_FIELDS, false); setFeature(FEATURE_SORT_ALL_COLUMNS_WHEN_NO_PRIMARY_KEY, false); @@ -280,7 +281,24 @@ public Object getProperty(String name) return _propertyMap.get(name); } - private Object convertIfNeeded(String property, Object value) + /** + * Copies every known property and feature value from this config into the given target config. + * + * @param target The config to receive this config's property and feature values. + * @since 3.4.1 + */ + public void copyPropertiesInto(DatabaseConfig target) + { + logger.trace("copyPropertiesInto(target={}) - start", target); + + for (ConfigProperty configProperty : ALL_PROPERTIES) + { + String property = configProperty.getProperty(); + target.setProperty(property, getProperty(property)); + } + } + + private Object convertIfNeeded(String property, Object value) { logger.trace("convertIfNeeded(property={}, value={}) - start", property, value); diff --git a/src/test/java/org/dbunit/DataSourceDatabaseTesterIT.java b/src/test/java/org/dbunit/DataSourceDatabaseTesterIT.java index d6d7da277..dfd7c94fd 100644 --- a/src/test/java/org/dbunit/DataSourceDatabaseTesterIT.java +++ b/src/test/java/org/dbunit/DataSourceDatabaseTesterIT.java @@ -28,6 +28,7 @@ import javax.sql.DataSource; import org.dbunit.database.CachingConnectionProvider; +import org.dbunit.database.DatabaseConfig; import org.dbunit.database.IDatabaseConnection; import org.h2.jdbcx.JdbcDataSource; import org.junit.jupiter.api.AfterEach; @@ -118,6 +119,53 @@ void testConstructor_withNullDataSourceAndProvider_throwsNullPointerException() .isInstanceOf(NullPointerException.class); } + @Test + void testGetConnection_withDatabaseConfig_appliesConfiguredPropertiesToConnection() + throws Exception + { + final DatabaseConfig databaseConfig = new DatabaseConfig(); + databaseConfig.setProperty(DatabaseConfig.PROPERTY_BATCH_SIZE, 500); + final DataSourceDatabaseTester tester = + new DataSourceDatabaseTester(newDataSource(), null, null, databaseConfig); + + final IDatabaseConnection connection = tester.getConnection(); + + openedConnection = connection; + assertThat(connection.getConfig().getProperty(DatabaseConfig.PROPERTY_BATCH_SIZE)) + .as("The DatabaseConfig supplied to the constructor must be applied to every " + + "connection this tester creates.") + .isEqualTo(500); + } + + @Test + void testGetConnection_withDatabaseConfigAndProvider_appliesConfiguredPropertiesToCachedConnection() + throws Exception + { + final DatabaseConfig databaseConfig = new DatabaseConfig(); + databaseConfig.setProperty(DatabaseConfig.PROPERTY_BATCH_SIZE, 500); + final CachingConnectionProvider provider = new CachingConnectionProvider(); + final DataSourceDatabaseTester tester = + new DataSourceDatabaseTester(newDataSource(), null, provider, databaseConfig); + + final IDatabaseConnection connection = tester.getConnection(); + + openedConnection = connection; + assertThat(connection.getConfig().getProperty(DatabaseConfig.PROPERTY_BATCH_SIZE)) + .as("The DatabaseConfig supplied to the constructor must be applied even when a " + + "CachingConnectionProvider is used.") + .isEqualTo(500); + } + + @Test + void testConstructor_withNullDataSourceAndProviderAndConfig_throwsNullPointerException() + { + assertThatThrownBy(() -> new DataSourceDatabaseTester(null, null, + new CachingConnectionProvider(), new DatabaseConfig())) + .as("The 4-arg constructor must reject a null DataSource just like the " + + "pre-existing constructors do.") + .isInstanceOf(NullPointerException.class); + } + private static DataSource newDataSource() { final JdbcDataSource dataSource = new JdbcDataSource(); diff --git a/src/test/java/org/dbunit/database/DatabaseConfigTest.java b/src/test/java/org/dbunit/database/DatabaseConfigTest.java index a9e2ddbca..3a8a97252 100644 --- a/src/test/java/org/dbunit/database/DatabaseConfigTest.java +++ b/src/test/java/org/dbunit/database/DatabaseConfigTest.java @@ -142,4 +142,39 @@ void testSetFeatureViaSetFeatureMethod_withBooleanTrue_setsFeatureToTrue() throw .isTrue(); } + @Test + void testCopyPropertiesInto_withConfiguredPropertyAndFeature_copiesValuesToTargetConfig() + throws Exception + { + final DatabaseConfig source = new DatabaseConfig(); + source.setProperty(DatabaseConfig.PROPERTY_BATCH_SIZE, 500); + source.setFeature(DatabaseConfig.FEATURE_BATCHED_STATEMENTS, true); + final DatabaseConfig target = new DatabaseConfig(); + + source.copyPropertiesInto(target); + + assertThat(target.getProperty(DatabaseConfig.PROPERTY_BATCH_SIZE)) + .as("copyPropertiesInto() must copy a configured property value to the target config.") + .isEqualTo(500); + assertThat(target.getFeature(DatabaseConfig.FEATURE_BATCHED_STATEMENTS)) + .as("copyPropertiesInto() must copy a configured feature value to the target config.") + .isTrue(); + } + + @Test + void testCopyPropertiesInto_withNullablePropertyAtDefault_overwritesTargetWithNull() + throws Exception + { + final DatabaseConfig source = new DatabaseConfig(); + final DatabaseConfig target = new DatabaseConfig(); + target.setProperty(DatabaseConfig.PROPERTY_ESCAPE_PATTERN, "[?]"); + + source.copyPropertiesInto(target); + + assertThat(target.getProperty(DatabaseConfig.PROPERTY_ESCAPE_PATTERN)) + .as("copyPropertiesInto() must overwrite the target's property even when the " + + "source left it at its nullable default.") + .isNull(); + } + }