diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java index a020bedb02852..d9042f9ee9173 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigurationException.java @@ -1,6 +1,7 @@ package io.quarkus.runtime.configuration; import java.util.Collections; +import java.util.HashSet; import java.util.Set; import io.quarkus.dev.config.ConfigurationProblem; @@ -55,7 +56,7 @@ public ConfigurationException(final String msg, Set configKeys) { */ public ConfigurationException(final Throwable cause, Set configKeys) { super(cause); - this.configKeys = configKeys; + this.configKeys = forwardCauseConfigKeys(configKeys, cause); } /** @@ -77,7 +78,7 @@ public ConfigurationException(final String msg, final Throwable cause) { */ public ConfigurationException(final String msg, final Throwable cause, Set configKeys) { super(msg, cause); - this.configKeys = configKeys; + this.configKeys = forwardCauseConfigKeys(configKeys, cause); } public ConfigurationException(Throwable cause) { @@ -88,4 +89,12 @@ public ConfigurationException(Throwable cause) { public Set getConfigKeys() { return configKeys; } + + private static Set forwardCauseConfigKeys(Set configKeys, Throwable cause) { + if (cause instanceof ConfigurationProblem) { + var merged = new HashSet(configKeys); + merged.addAll(((ConfigurationProblem) cause).getConfigKeys()); + } + return configKeys; + } } diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index f7a7040fe886d..b52cb438b06dc 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -14,8 +14,8 @@ import javax.sql.XADataSource; +import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Default; -import jakarta.inject.Singleton; import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; @@ -72,6 +72,7 @@ class AgroalProcessor { private static final String OPEN_TELEMETRY_DRIVER = "io.opentelemetry.instrumentation.jdbc.OpenTelemetryDriver"; private static final DotName DATA_SOURCE = DotName.createSimple(javax.sql.DataSource.class.getName()); + private static final DotName AGROAL_DATA_SOURCE = DotName.createSimple(AgroalDataSource.class.getName()); @BuildStep void agroal(BuildProducer feature) { @@ -277,7 +278,8 @@ void generateDataSourceBeans(AgroalRecorder recorder, SyntheticBeanBuildItem.ExtendedBeanConfigurator configurator = SyntheticBeanBuildItem .configure(AgroalDataSource.class) .addType(DATA_SOURCE) - .scope(Singleton.class) + .addType(AGROAL_DATA_SOURCE) + .scope(ApplicationScoped.class) .setRuntimeInit() .unremovable() .addInjectionPoint(ClassType.create(DotName.createSimple(DataSources.class))) diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/EagerStartupTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/EagerStartupTest.java new file mode 100644 index 0000000000000..838c8ad9a7131 --- /dev/null +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/EagerStartupTest.java @@ -0,0 +1,41 @@ +package io.quarkus.agroal.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.agroal.runtime.DataSources; +import io.quarkus.arc.Arc; +import io.quarkus.datasource.common.runtime.DataSourceUtil; +import io.quarkus.test.QuarkusUnitTest; + +/** + * Check that datasources are created eagerly on application startup. + *

+ * This has always been the case historically, so we want to keep it that way. + */ +public class EagerStartupTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("base.properties"); + + @Test + public void shouldStartEagerly() { + var container = Arc.container(); + var instanceHandle = container.instance(DataSources.class); + // Check that the following call won't trigger a lazy initialization: + // the DataSources bean must be eagerly initialized. + assertThat(container.getActiveContext(Singleton.class).getState() + .getContextualInstances().get(instanceHandle.getBean())) + .as("Eagerly instantiated DataSources bean") + .isNotNull(); + // Check that the datasource has already been eagerly created. + assertThat(instanceHandle.get().isDataSourceCreated(DataSourceUtil.DEFAULT_DATASOURCE_NAME)) + .isTrue(); + } + +} diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NoConfigTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NoConfigTest.java index 9e6c4eaade823..9d8e268078bce 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NoConfigTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NoConfigTest.java @@ -1,19 +1,94 @@ package io.quarkus.agroal.test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import java.sql.SQLException; +import javax.sql.DataSource; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import io.agroal.api.AgroalDataSource; +import io.quarkus.arc.Arc; +import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.test.QuarkusUnitTest; +/** + * We should be able to start the application, even with no configuration at all. + */ public class NoConfigTest { @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest(); + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + @Inject + MyBean myBean; @Test - public void testNoConfig() throws SQLException { - // we should be able to start the application, even with no configuration at all + public void dataSource_default() { + DataSource ds = Arc.container().instance(DataSource.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(ds).isNotNull(); + // However, if unconfigured, any attempt to use it at runtime will fail. + assertThatThrownBy(() -> ds.getConnection()) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("quarkus.datasource.jdbc.url has not been defined"); + } + + @Test + public void agroalDataSource_default() { + AgroalDataSource ds = Arc.container().instance(AgroalDataSource.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(ds).isNotNull(); + // However, if unconfigured, any attempt to use it at runtime will fail. + assertThatThrownBy(() -> ds.getConnection()) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("quarkus.datasource.jdbc.url has not been defined"); + } + + @Test + public void dataSource_named() { + DataSource ds = Arc.container().instance(DataSource.class, + new io.quarkus.agroal.DataSource.DataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(ds).isNull(); + } + + @Test + public void agroalDataSource_named() { + AgroalDataSource ds = Arc.container().instance(AgroalDataSource.class, + new io.quarkus.agroal.DataSource.DataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(ds).isNull(); + } + + @Test + public void injectedBean_default() { + assertThatThrownBy(() -> myBean.useDataSource()) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining("quarkus.datasource.jdbc.url has not been defined"); + } + + @ApplicationScoped + public static class MyBean { + @Inject + DataSource ds; + + public void useDataSource() throws SQLException { + ds.getConnection(); + } } } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java index 341e2cca19966..0e6e827b06a42 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java @@ -13,6 +13,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import jakarta.annotation.PostConstruct; import jakarta.annotation.PreDestroy; import jakarta.enterprise.inject.Any; import jakarta.enterprise.inject.Default; @@ -122,6 +123,10 @@ public static AgroalDataSource fromName(String dataSourceName) { .getDataSource(dataSourceName); } + public boolean isDataSourceCreated(String dataSourceName) { + return dataSources.containsKey(dataSourceName); + } + public AgroalDataSource getDataSource(String dataSourceName) { return dataSources.computeIfAbsent(dataSourceName, new Function() { @Override @@ -131,6 +136,13 @@ public AgroalDataSource apply(String s) { }); } + @PostConstruct + public void start() { + for (String dataSourceName : dataSourceSupport.entries.keySet()) { + getDataSource(dataSourceName); + } + } + @SuppressWarnings("resource") public AgroalDataSource doCreateDataSource(String dataSourceName) { if (!dataSourceSupport.entries.containsKey(dataSourceName)) { @@ -140,6 +152,7 @@ public AgroalDataSource doCreateDataSource(String dataSourceName) { DataSourceJdbcBuildTimeConfig dataSourceJdbcBuildTimeConfig = dataSourcesJdbcBuildTimeConfig .dataSources().get(dataSourceName).jdbc(); DataSourceRuntimeConfig dataSourceRuntimeConfig = dataSourcesRuntimeConfig.dataSources().get(dataSourceName); + DataSourceJdbcRuntimeConfig dataSourceJdbcRuntimeConfig = dataSourcesJdbcRuntimeConfig .getDataSourceJdbcRuntimeConfig(dataSourceName); diff --git a/extensions/datasource/common/pom.xml b/extensions/datasource/common/pom.xml index 6527c853e4c84..acdedbcd44a0c 100644 --- a/extensions/datasource/common/pom.xml +++ b/extensions/datasource/common/pom.xml @@ -12,6 +12,10 @@ quarkus-datasource-common Quarkus - Datasource - Common + + io.quarkus + quarkus-core + org.junit.jupiter junit-jupiter diff --git a/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java b/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java index f0a4b3378f1ba..7b11b9e4aab7a 100644 --- a/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java +++ b/extensions/datasource/common/src/main/java/io/quarkus/datasource/common/runtime/DataSourceUtil.java @@ -2,6 +2,10 @@ import java.util.Collection; import java.util.List; +import java.util.Locale; +import java.util.Set; + +import io.quarkus.runtime.configuration.ConfigurationException; public final class DataSourceUtil { @@ -34,6 +38,18 @@ public static List dataSourcePropertyKeys(String datasourceName, String } } + public static ConfigurationException dataSourceNotConfigured(String dataSourceName) { + return new ConfigurationException(String.format(Locale.ROOT, + "Datasource '%s' is not configured." + + " To solve this, configure datasource '%s'." + + " Refer to https://quarkus.io/guides/datasource for guidance.", + dataSourceName, dataSourceName), + Set.of(dataSourcePropertyKey(dataSourceName, "db-kind"), + dataSourcePropertyKey(dataSourceName, "username"), + dataSourcePropertyKey(dataSourceName, "password"), + dataSourcePropertyKey(dataSourceName, "jdbc.url"))); + } + private DataSourceUtil() { } diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java index cccdf480f078b..22b6650110044 100644 --- a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java +++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java @@ -199,7 +199,7 @@ private RunningDevService startDevDb( LaunchMode launchMode, Optional consoleInstalledBuildItem, LoggingSetupBuildItem loggingSetupBuildItem, GlobalDevServicesConfig globalDevServicesConfig) { boolean explicitlyDisabled = !(dataSourceBuildTimeConfig.devservices().enabled().orElse(true)); - String dataSourcePrettyName = DataSourceUtil.isDefault(dbName) ? "default datasource" : "datasource" + dbName; + String dataSourcePrettyName = DataSourceUtil.isDefault(dbName) ? "default datasource" : "datasource " + dbName; if (explicitlyDisabled) { //explicitly disabled diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyDefaultDatasourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyDefaultDatasourceTest.java new file mode 100644 index 0000000000000..c172cdecb9a73 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyDefaultDatasourceTest.java @@ -0,0 +1,62 @@ +package io.quarkus.flyway.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.CreationException; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class FlywayExtensionConfigEmptyDefaultDatasourceTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + @Inject + Instance flywayForDefaultDatasource; + + @Inject + MyBean myBean; + + @Test + @DisplayName("If there is no config for the default datasource, the application should boot, but Flyway should be deactivated for that datasource") + public void testBootSucceedsButFlywayDeactivated() { + assertThatThrownBy(flywayForDefaultDatasource::get) + .isInstanceOf(CreationException.class) + .cause() + .hasMessageContainingAll("Unable to find datasource '' for Flyway", + "Datasource '' is not configured.", + "To solve this, configure datasource ''.", + "Refer to https://quarkus.io/guides/datasource for guidance."); + } + + @Test + @DisplayName("If there is no config for the default datasource, the application should boot even if we inject a bean that depends on Liquibase, but actually using Liquibase should fail") + public void testBootSucceedsWithInjectedBeanDependingOnFlywayButFlywayDeactivated() { + assertThatThrownBy(() -> myBean.useFlyway()) + .cause() + .hasMessageContainingAll("Unable to find datasource '' for Flyway", + "Datasource '' is not configured.", + "To solve this, configure datasource ''.", + "Refer to https://quarkus.io/guides/datasource for guidance."); + } + + @ApplicationScoped + public static class MyBean { + @Inject + Flyway flywayForDefaultDatasource; + + public void useFlyway() { + flywayForDefaultDatasource.getConfiguration(); + } + } +} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyNamedDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyNamedDataSourceTest.java new file mode 100644 index 0000000000000..a195ce306b47c --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyNamedDataSourceTest.java @@ -0,0 +1,41 @@ +package io.quarkus.flyway.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.UnsatisfiedResolutionException; +import jakarta.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.flyway.FlywayDataSource; +import io.quarkus.test.QuarkusUnitTest; + +public class FlywayExtensionConfigEmptyNamedDataSourceTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // We need this otherwise the *default* datasource may impact this test + .overrideConfigKey("quarkus.datasource.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.username", "sa") + .overrideConfigKey("quarkus.datasource.password", "sa") + .overrideConfigKey("quarkus.datasource.jdbc.url", + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1") + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + @Inject + @FlywayDataSource("users") + Instance flywayForNamedDatasource; + + @Test + @DisplayName("If there is no config for a named datasource, the application should boot, but Flyway should be deactivated for that datasource") + public void testBootSucceedsButFlywayDeactivated() { + assertThatThrownBy(flywayForNamedDatasource::get) + .isInstanceOf(UnsatisfiedResolutionException.class) + .hasMessageContaining("No bean found"); + } +} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java deleted file mode 100644 index d5f680c6d9ef6..0000000000000 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigEmptyTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.quarkus.flyway.test; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import jakarta.enterprise.inject.Instance; -import jakarta.enterprise.inject.UnsatisfiedResolutionException; -import jakarta.inject.Inject; - -import org.flywaydb.core.Flyway; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.test.QuarkusUnitTest; - -/** - * Flyway needs a datasource to work. - * This tests assures, that an error occurs, - * as soon as the default flyway configuration points to a missing default datasource. - */ -public class FlywayExtensionConfigEmptyTest { - - @Inject - Instance flyway; - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addAsResource("config-empty.properties", "application.properties")); - - @Test - @DisplayName("Injecting (default) flyway should fail if there is no datasource configured") - public void testFlywayNotAvailableWithoutDataSource() { - assertThrows(UnsatisfiedResolutionException.class, flyway::get); - } -} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java deleted file mode 100644 index af5c7cca818f5..0000000000000 --- a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionConfigMissingNamedDataSourceTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.quarkus.flyway.test; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import jakarta.enterprise.inject.Instance; -import jakarta.enterprise.inject.UnsatisfiedResolutionException; -import jakarta.inject.Inject; - -import org.flywaydb.core.Flyway; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.flyway.FlywayDataSource; -import io.quarkus.test.QuarkusUnitTest; - -/** - * Flyway needs a datasource to work. - * This tests assures that an error occurs as soon as a named flyway configuration points to a missing datasource. - */ -public class FlywayExtensionConfigMissingNamedDataSourceTest { - - @Inject - @FlywayDataSource("users") - Instance flyway; - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addAsResource("config-for-missing-named-datasource.properties", "application.properties")); - - @Test - @DisplayName("Injecting flyway should fail if the named datasource is missing") - public void testFlywayNotAvailableWithoutDataSource() { - assertThrows(UnsatisfiedResolutionException.class, flyway::get); - } -} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigEmptyTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigEmptyTest.java new file mode 100644 index 0000000000000..0f9f506d16d0a --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartDefaultDatasourceConfigEmptyTest.java @@ -0,0 +1,41 @@ +package io.quarkus.flyway.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.inject.CreationException; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class FlywayExtensionMigrateAtStartDefaultDatasourceConfigEmptyTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource("db/migration/V1.0.0__Quarkus.sql")) + .overrideConfigKey("quarkus.flyway.migrate-at-start", "true") + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + @Inject + Instance flywayForDefaultDatasource; + + @Test + @DisplayName("If there is no config for the default datasource, even if migrate-at-start is enabled, the application should boot, but Flyway should be deactivated for that datasource") + public void testBootSucceedsButFlywayDeactivated() { + assertThatThrownBy(flywayForDefaultDatasource::get) + .isInstanceOf(CreationException.class) + .cause() + .hasMessageContainingAll("Unable to find datasource '' for Flyway", + "Datasource '' is not configured.", + "To solve this, configure datasource ''.", + "Refer to https://quarkus.io/guides/datasource for guidance."); + } + +} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigEmptyTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigEmptyTest.java new file mode 100644 index 0000000000000..48e507e40783d --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartNamedDatasourceConfigEmptyTest.java @@ -0,0 +1,44 @@ +package io.quarkus.flyway.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.UnsatisfiedResolutionException; +import jakarta.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.flyway.FlywayDataSource; +import io.quarkus.test.QuarkusUnitTest; + +public class FlywayExtensionMigrateAtStartNamedDatasourceConfigEmptyTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource("db/migration/V1.0.0__Quarkus.sql")) + .overrideConfigKey("quarkus.flyway.users.migrate-at-start", "true") + // We need this otherwise the *default* datasource may impact this test + .overrideConfigKey("quarkus.datasource.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.username", "sa") + .overrideConfigKey("quarkus.datasource.password", "sa") + .overrideConfigKey("quarkus.datasource.jdbc.url", + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1") + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + @Inject + @FlywayDataSource("users") + Instance flywayForNamedDatasource; + + @Test + @DisplayName("If there is no config for a named datasource, even if migrate-at-start is enabled, the application should boot, but Flyway should be deactivated for that datasource") + public void testBootSucceedsButFlywayDeactivated() { + assertThatThrownBy(flywayForNamedDatasource::get) + .isInstanceOf(UnsatisfiedResolutionException.class) + .hasMessageContaining("No bean found"); + } +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java index f8df08e4e7825..4e858dcf71a3b 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRecorder.java @@ -2,6 +2,7 @@ import java.lang.annotation.Annotation; import java.util.Collection; +import java.util.Locale; import java.util.Map; import java.util.function.Function; @@ -31,6 +32,7 @@ import io.quarkus.flyway.FlywayDataSource.FlywayDataSourceLiteral; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.runtime.configuration.ConfigurationException; @Recorder public class FlywayRecorder { @@ -64,15 +66,24 @@ public Function, FlywayContainer> fl return new Function<>() { @Override public FlywayContainer apply(SyntheticCreationalContext context) { - DataSource dataSource = context.getInjectedReference(DataSources.class).getDataSource(dataSourceName); - if (dataSource instanceof UnconfiguredDataSource) { - return new UnconfiguredDataSourceFlywayContainer(dataSourceName); + DataSource dataSource; + try { + dataSource = context.getInjectedReference(DataSources.class).getDataSource(dataSourceName); + if (dataSource instanceof UnconfiguredDataSource) { + throw DataSourceUtil.dataSourceNotConfigured(dataSourceName); + } + } catch (ConfigurationException e) { + // TODO do we really want to enable retrieval of a FlywayContainer for an unconfigured datasource? + // Assigning ApplicationScoped to the FlywayContainer + // and throwing UnsatisfiedResolutionException on bean creation (first access) + // would probably make more sense. + return new UnconfiguredDataSourceFlywayContainer(dataSourceName, String.format(Locale.ROOT, + "Unable to find datasource '%s' for Flyway: %s", + dataSourceName, e.getMessage()), e); } FlywayContainerProducer flywayProducer = context.getInjectedReference(FlywayContainerProducer.class); - FlywayContainer flywayContainer = flywayProducer.createFlyway(dataSource, dataSourceName, hasMigrations, - createPossible); - return flywayContainer; + return flywayProducer.createFlyway(dataSource, dataSourceName, hasMigrations, createPossible); } }; } diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/UnconfiguredDataSourceFlywayContainer.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/UnconfiguredDataSourceFlywayContainer.java index a3206cd8141ae..5011c9898ce0d 100644 --- a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/UnconfiguredDataSourceFlywayContainer.java +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/UnconfiguredDataSourceFlywayContainer.java @@ -4,13 +4,17 @@ public class UnconfiguredDataSourceFlywayContainer extends FlywayContainer { - public UnconfiguredDataSourceFlywayContainer(String dataSourceName) { + private final String message; + private final Throwable cause; + + public UnconfiguredDataSourceFlywayContainer(String dataSourceName, String message, Throwable cause) { super(null, false, false, false, false, false, dataSourceName, false, false); + this.message = message; + this.cause = cause; } @Override public Flyway getFlyway() { - throw new UnsupportedOperationException( - "Cannot get a Flyway instance for unconfigured datasource " + getDataSourceName()); + throw new UnsupportedOperationException(message, cause); } } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 7c90798d1ade0..2ad2bb9e78ac7 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -871,13 +871,10 @@ private void handleHibernateORMWithNoPersistenceXml( && (!hibernateOrmConfig.defaultPersistenceUnit.datasource.isPresent() || DataSourceUtil.isDefault(hibernateOrmConfig.defaultPersistenceUnit.datasource.get())) && !defaultJdbcDataSource.isPresent()) { - throw new ConfigurationException( - "Model classes are defined for the default persistence unit, but no default datasource was found." - + " The default EntityManagerFactory will not be created." - + " To solve this, configure the default datasource." - + " Refer to https://quarkus.io/guides/datasource for guidance.", - new HashSet<>(Arrays.asList("quarkus.datasource.db-kind", "quarkus.datasource.username", - "quarkus.datasource.password", "quarkus.datasource.jdbc.url"))); + String persistenceUnitName = PersistenceUnitUtil.DEFAULT_PERSISTENCE_UNIT_NAME; + String dataSourceName = DataSourceUtil.DEFAULT_DATASOURCE_NAME; + throw PersistenceUnitUtil.unableToFindDataSource(persistenceUnitName, dataSourceName, + DataSourceUtil.dataSourceNotConfigured(dataSourceName)); } for (Entry persistenceUnitEntry : hibernateOrmConfig.persistenceUnits @@ -1228,14 +1225,12 @@ private static void collectDialectConfigForPersistenceXml(String persistenceUnit private static Optional findJdbcDataSource(String persistenceUnitName, HibernateOrmConfigPersistenceUnit persistenceUnitConfig, List jdbcDataSources) { if (persistenceUnitConfig.datasource.isPresent()) { + String dataSourceName = persistenceUnitConfig.datasource.get(); return Optional.of(jdbcDataSources.stream() - .filter(i -> persistenceUnitConfig.datasource.get().equals(i.getName())) + .filter(i -> dataSourceName.equals(i.getName())) .findFirst() - .orElseThrow(() -> new ConfigurationException(String.format(Locale.ROOT, - "The datasource '%1$s' is not configured but the persistence unit '%2$s' uses it." - + " To solve this, configure datasource '%1$s'." - + " Refer to https://quarkus.io/guides/datasource for guidance.", - persistenceUnitConfig.datasource.get(), persistenceUnitName)))); + .orElseThrow(() -> PersistenceUnitUtil.unableToFindDataSource(persistenceUnitName, dataSourceName, + DataSourceUtil.dataSourceNotConfigured(dataSourceName)))); } else if (PersistenceUnitUtil.isDefaultPersistenceUnit(persistenceUnitName)) { return jdbcDataSources.stream() .filter(i -> i.isDefault()) diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java deleted file mode 100644 index 4285499aad472..0000000000000 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.quarkus.hibernate.orm.config; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.runtime.configuration.ConfigurationException; -import io.quarkus.test.QuarkusUnitTest; - -public class EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest { - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .assertException(t -> { - assertThat(t) - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll( - "The datasource 'ds-1' is not configured but the persistence unit '' uses it.", - "To solve this, configure datasource 'ds-1'.", - "Refer to https://quarkus.io/guides/datasource for guidance."); - }) - .withApplicationRoot((jar) -> jar - .addClass(MyEntity.class) - .addAsResource("application-default-pu-explicit-unconfigured-datasource.properties", - "application.properties")); - - @Test - public void testInvalidConfiguration() { - // deployment exception should happen first - Assertions.fail(); - } - -} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java deleted file mode 100644 index 41da125a13c78..0000000000000 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.quarkus.hibernate.orm.config; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.runtime.configuration.ConfigurationException; -import io.quarkus.test.QuarkusUnitTest; - -public class EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest { - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .assertException(t -> { - assertThat(t) - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll( - "Model classes are defined for the default persistence unit, but no default datasource was found. The default EntityManagerFactory will not be created. To solve this, configure the default datasource. Refer to https://quarkus.io/guides/datasource for guidance."); - }) - .withApplicationRoot((jar) -> jar - .addClass(MyEntity.class)) - .overrideConfigKey("quarkus.datasource.devservices.enabled", "false"); - - @Test - public void testInvalidConfiguration() { - // deployment exception should happen first - Assertions.fail(); - } - -} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java new file mode 100644 index 0000000000000..95da175428278 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java @@ -0,0 +1,35 @@ +package io.quarkus.hibernate.orm.config.datasource; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.config.MyEntity; +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.test.QuarkusUnitTest; + +public class EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(MyEntity.class)) + .overrideConfigKey("quarkus.hibernate-orm.datasource", "ds-1") + .overrideConfigKey("quarkus.hibernate-orm.database.generation", "drop-and-create") + .assertException(t -> assertThat(t) + .isInstanceOf(ConfigurationException.class) + .hasMessageContainingAll( + "Unable to find datasource 'ds-1' for persistence unit ''", + "Datasource 'ds-1' is not configured.", + "To solve this, configure datasource 'ds-1'.", + "Refer to https://quarkus.io/guides/datasource for guidance.")); + + @Test + public void testInvalidConfiguration() { + // deployment exception should happen first + Assertions.fail(); + } + +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java new file mode 100644 index 0000000000000..5e301b02be941 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java @@ -0,0 +1,35 @@ +package io.quarkus.hibernate.orm.config.datasource; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.config.MyEntity; +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.test.QuarkusUnitTest; + +public class EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(MyEntity.class)) + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(t -> assertThat(t) + .isInstanceOf(ConfigurationException.class) + .hasMessageContainingAll( + "Unable to find datasource '' for persistence unit ''", + "Datasource '' is not configured.", + "To solve this, configure datasource ''.", + "Refer to https://quarkus.io/guides/datasource for guidance.")); + + @Test + public void testInvalidConfiguration() { + // deployment exception should happen first + Assertions.fail(); + } + +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitUnconfiguredDatasourceTest.java new file mode 100644 index 0000000000000..bc7f76483c09a --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithExplicitUnconfiguredDatasourceTest.java @@ -0,0 +1,35 @@ +package io.quarkus.hibernate.orm.config.datasource; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.config.namedpu.MyEntity; +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.test.QuarkusUnitTest; + +public class EntitiesInNamedPUWithExplicitUnconfiguredDatasourceTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addPackage(MyEntity.class.getPackage().getName())) + .overrideConfigKey("quarkus.hibernate-orm.pu-1.datasource", "ds-1") + .overrideConfigKey("quarkus.hibernate-orm.pu-1.database.generation", "drop-and-create") + .assertException(t -> assertThat(t) + .isInstanceOf(ConfigurationException.class) + .hasMessageContainingAll( + "Unable to find datasource 'ds-1' for persistence unit 'pu-1'", + "Datasource 'ds-1' is not configured.", + "To solve this, configure datasource 'ds-1'.", + "Refer to https://quarkus.io/guides/datasource for guidance.")); + + @Test + public void testInvalidConfiguration() { + // deployment exception should happen first + Assertions.fail(); + } + +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithoutDatasourceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithoutDatasourceTest.java new file mode 100644 index 0000000000000..b2ca39823cdbb --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/datasource/EntitiesInNamedPUWithoutDatasourceTest.java @@ -0,0 +1,34 @@ +package io.quarkus.hibernate.orm.config.datasource; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.orm.config.namedpu.MyEntity; +import io.quarkus.runtime.configuration.ConfigurationException; +import io.quarkus.test.QuarkusUnitTest; + +public class EntitiesInNamedPUWithoutDatasourceTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addPackage(MyEntity.class.getPackage().getName())) + // There will still be a default datasource if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false") + // We need at least one build-time property, otherwise the PU gets ignored... + .overrideConfigKey("quarkus.hibernate-orm.pu-1.packages", MyEntity.class.getPackageName()) + .overrideConfigKey("quarkus.hibernate-orm.pu-1.database.generation", "drop-and-create") + .assertException(t -> assertThat(t) + .isInstanceOf(ConfigurationException.class) + .hasMessageContainingAll("Datasource must be defined for persistence unit 'pu-1'."));; + + @Test + public void testInvalidConfiguration() { + // deployment exception should happen first + Assertions.fail(); + } + +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/namedpu/EntitiesInNamedPUWithExplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/namedpu/EntitiesInNamedPUWithExplicitUnconfiguredDatasourceTest.java deleted file mode 100644 index fdcaf43005835..0000000000000 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/namedpu/EntitiesInNamedPUWithExplicitUnconfiguredDatasourceTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.quarkus.hibernate.orm.config.namedpu; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.runtime.configuration.ConfigurationException; -import io.quarkus.test.QuarkusUnitTest; - -public class EntitiesInNamedPUWithExplicitUnconfiguredDatasourceTest { - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .assertException(t -> { - assertThat(t) - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll( - "The datasource 'ds-1' is not configured but the persistence unit 'pu-1' uses it.", - "To solve this, configure datasource 'ds-1'.", - "Refer to https://quarkus.io/guides/datasource for guidance."); - }) - .withApplicationRoot((jar) -> jar - .addPackage(MyEntity.class.getPackage().getName()) - .addAsResource("application-named-pu-explicit-unconfigured-datasource.properties", - "application.properties")); - - @Test - public void testInvalidConfiguration() { - // deployment exception should happen first - Assertions.fail(); - } - -} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/namedpu/EntitiesInNamedPUWithoutDatasourceTest.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/namedpu/EntitiesInNamedPUWithoutDatasourceTest.java deleted file mode 100644 index 552e9079dcd5b..0000000000000 --- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/config/namedpu/EntitiesInNamedPUWithoutDatasourceTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.quarkus.hibernate.orm.config.namedpu; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.runtime.configuration.ConfigurationException; -import io.quarkus.test.QuarkusUnitTest; - -public class EntitiesInNamedPUWithoutDatasourceTest { - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .assertException(t -> { - assertThat(t) - .isInstanceOf(ConfigurationException.class) - .hasMessageContainingAll("Datasource must be defined for persistence unit 'pu-1'."); - }) - .withConfigurationResource("application-named-pu-no-datasource.properties") - .overrideConfigKey("quarkus.datasource.devservices.enabled", "false") - .withApplicationRoot((jar) -> jar - .addPackage(MyEntity.class.getPackage().getName())); - - @Test - public void testInvalidConfiguration() { - // deployment exception should happen first - Assertions.fail(); - } - -} diff --git a/extensions/hibernate-orm/deployment/src/test/resources/application-default-pu-explicit-unconfigured-datasource.properties b/extensions/hibernate-orm/deployment/src/test/resources/application-default-pu-explicit-unconfigured-datasource.properties deleted file mode 100644 index 6bc5049280142..0000000000000 --- a/extensions/hibernate-orm/deployment/src/test/resources/application-default-pu-explicit-unconfigured-datasource.properties +++ /dev/null @@ -1,3 +0,0 @@ -quarkus.hibernate-orm.datasource=ds-1 -#quarkus.hibernate-orm.log.sql=true -quarkus.hibernate-orm.database.generation=drop-and-create diff --git a/extensions/hibernate-orm/deployment/src/test/resources/application-named-pu-explicit-unconfigured-datasource.properties b/extensions/hibernate-orm/deployment/src/test/resources/application-named-pu-explicit-unconfigured-datasource.properties deleted file mode 100644 index 4d7291dba7fcb..0000000000000 --- a/extensions/hibernate-orm/deployment/src/test/resources/application-named-pu-explicit-unconfigured-datasource.properties +++ /dev/null @@ -1,3 +0,0 @@ -quarkus.hibernate-orm.pu-1.datasource=ds-1 -quarkus.hibernate-orm.pu-1.log.sql=true -quarkus.hibernate-orm.pu-1.database.generation=drop-and-create diff --git a/extensions/hibernate-orm/deployment/src/test/resources/application-named-pu-no-datasource.properties b/extensions/hibernate-orm/deployment/src/test/resources/application-named-pu-no-datasource.properties deleted file mode 100644 index 422410bbf6fe1..0000000000000 --- a/extensions/hibernate-orm/deployment/src/test/resources/application-named-pu-no-datasource.properties +++ /dev/null @@ -1,4 +0,0 @@ -# We need at least one build-time property, otherwise the PU gets ignored... -quarkus.hibernate-orm.pu-1.packages=io.quarkus.hibernate.orm.config.namedpu -quarkus.hibernate-orm.pu-1.log.sql=true -quarkus.hibernate-orm.pu-1.database.generation=drop-and-create diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java index 5db72232952c5..f98ba87694899 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java @@ -24,10 +24,9 @@ import org.hibernate.service.internal.ProvidedService; import org.jboss.logging.Logger; -import io.quarkus.agroal.DataSource.DataSourceLiteral; +import io.quarkus.agroal.runtime.DataSources; import io.quarkus.agroal.runtime.UnconfiguredDataSource; import io.quarkus.arc.Arc; -import io.quarkus.arc.InstanceHandle; import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.hibernate.orm.runtime.RuntimeSettings.Builder; import io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder; @@ -38,7 +37,6 @@ import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationRuntimeInitListener; import io.quarkus.hibernate.orm.runtime.recording.PrevalidatedQuarkusMetadata; import io.quarkus.hibernate.orm.runtime.recording.RecordedState; -import io.quarkus.runtime.configuration.ConfigurationException; /** * This can not inherit from HibernatePersistenceProvider as that would force @@ -375,7 +373,7 @@ private void verifyProperties(Map properties) { } } - private static void injectDataSource(String persistenceUnitName, String dataSource, + private static void injectDataSource(String persistenceUnitName, String dataSourceName, RuntimeSettings.Builder runtimeSettingsBuilder) { // first convert @@ -389,26 +387,16 @@ private static void injectDataSource(String persistenceUnitName, String dataSour return; } - InstanceHandle dataSourceHandle; - if (DataSourceUtil.isDefault(dataSource)) { - dataSourceHandle = Arc.container().instance(DataSource.class); - } else { - dataSourceHandle = Arc.container().instance(DataSource.class, new DataSourceLiteral(dataSource)); - } - - if (!dataSourceHandle.isAvailable()) { - throw new IllegalStateException( - "No datasource " + dataSource + " has been defined for persistence unit " + persistenceUnitName); - } - - DataSource ds = dataSourceHandle.get(); - if (ds instanceof UnconfiguredDataSource) { - throw new ConfigurationException( - "Model classes are defined for the default persistence unit " + persistenceUnitName - + " but configured datasource " + dataSource - + " not found: the default EntityManagerFactory will not be created. To solve this, configure the default datasource. Refer to https://quarkus.io/guides/datasource for guidance."); + DataSource dataSource; + try { + dataSource = Arc.container().instance(DataSources.class).get().getDataSource(dataSourceName); + if (dataSource instanceof UnconfiguredDataSource) { + throw DataSourceUtil.dataSourceNotConfigured(dataSourceName); + } + } catch (RuntimeException e) { + throw PersistenceUnitUtil.unableToFindDataSource(persistenceUnitName, dataSourceName, e); } - runtimeSettingsBuilder.put(AvailableSettings.DATASOURCE, ds); + runtimeSettingsBuilder.put(AvailableSettings.DATASOURCE, dataSource); } private static void injectRuntimeConfiguration(HibernateOrmRuntimeConfigPersistenceUnit persistenceUnitConfig, diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java index 481249f84ab5b..f7c05f7530e44 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java @@ -75,7 +75,8 @@ public void run() { } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { - throw new RuntimeException(e.getCause()); + throw e.getCause() instanceof RuntimeException ? (RuntimeException) e.getCause() + : new RuntimeException(e.getCause()); } } } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java index b91894c052e68..a3194b02f77ad 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitUtil.java @@ -13,6 +13,7 @@ import io.quarkus.arc.InjectableInstance; import io.quarkus.hibernate.orm.PersistenceUnit; import io.quarkus.hibernate.orm.PersistenceUnitExtension; +import io.quarkus.runtime.configuration.ConfigurationException; public class PersistenceUnitUtil { private static final Logger LOG = Logger.getLogger(PersistenceUnitUtil.class); @@ -104,4 +105,13 @@ public static InjectableInstance legacySingleExtensionInstanceForPersiste private static boolean isDefaultBean(InjectableInstance instance) { return instance.isResolvable() && instance.getHandle().getBean().isDefaultBean(); } + + public static ConfigurationException unableToFindDataSource(String persistenceUnitName, + String dataSourceName, + Throwable cause) { + return new ConfigurationException(String.format(Locale.ROOT, + "Unable to find datasource '%s' for persistence unit '%s': %s", + dataSourceName, persistenceUnitName, cause.getMessage()), + cause); + } } diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java new file mode 100644 index 0000000000000..c4c11e2307d01 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest.java @@ -0,0 +1,23 @@ +package io.quarkus.hibernate.reactive.config.datasource; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.config.MyEntity; +import io.quarkus.test.QuarkusUnitTest; + +public class EntitiesInDefaultPUWithExplicitUnconfiguredDatasourceTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(MyEntity.class)) + .overrideConfigKey("quarkus.hibernate-orm.datasource", "ds-1") + .overrideConfigKey("quarkus.hibernate-orm.database.generation", "drop-and-create"); + + @Test + public void testInvalidConfiguration() { + // bootstrap will succeed and ignore the fact that a datasource is unconfigured... + } + +} diff --git a/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java new file mode 100644 index 0000000000000..74f0f25029c80 --- /dev/null +++ b/extensions/hibernate-reactive/deployment/src/test/java/io/quarkus/hibernate/reactive/config/datasource/EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest.java @@ -0,0 +1,23 @@ +package io.quarkus.hibernate.reactive.config.datasource; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.hibernate.reactive.config.MyEntity; +import io.quarkus.test.QuarkusUnitTest; + +public class EntitiesInDefaultPUWithImplicitUnconfiguredDatasourceTest { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClass(MyEntity.class)) + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + @Test + public void testInvalidConfiguration() { + // bootstrap will succeed and ignore the fact that a datasource is unconfigured... + } + +} diff --git a/extensions/liquibase/deployment/pom.xml b/extensions/liquibase/deployment/pom.xml index 7a82d54cf07dd..9c3ba43b1a015 100644 --- a/extensions/liquibase/deployment/pom.xml +++ b/extensions/liquibase/deployment/pom.xml @@ -42,6 +42,11 @@ quarkus-junit5-internal test + + org.assertj + assertj-core + test + io.quarkus quarkus-test-h2 diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigEmptyDefaultDatasourceTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigEmptyDefaultDatasourceTest.java new file mode 100644 index 0000000000000..ac28eb20acf10 --- /dev/null +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigEmptyDefaultDatasourceTest.java @@ -0,0 +1,31 @@ +package io.quarkus.liquibase.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class LiquibaseExtensionConfigEmptyDefaultDatasourceTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(t -> assertThat(t).cause().cause() + .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + "Datasource '' is not configured.", + "To solve this, configure datasource ''.", + "Refer to https://quarkus.io/guides/datasource for guidance.")); + + @Test + @DisplayName("If there is no config for the default datasource, the application should fail to boot") + public void testBootFails() { + // Should not be reached because boot should fail. + assertTrue(false); + } + +} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigEmptyNamedDatasourceTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigEmptyNamedDatasourceTest.java new file mode 100644 index 0000000000000..8e532e29c17a0 --- /dev/null +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigEmptyNamedDatasourceTest.java @@ -0,0 +1,40 @@ +package io.quarkus.liquibase.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.UnsatisfiedResolutionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.liquibase.LiquibaseDataSource; +import io.quarkus.liquibase.LiquibaseFactory; +import io.quarkus.test.QuarkusUnitTest; + +public class LiquibaseExtensionConfigEmptyNamedDatasourceTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false") + // We need this otherwise it's going to be the *default* datasource making everything fail + .overrideConfigKey("quarkus.datasource.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.username", "sa") + .overrideConfigKey("quarkus.datasource.password", "sa") + .overrideConfigKey("quarkus.datasource.jdbc.url", + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1"); + @Inject + @LiquibaseDataSource("users") + Instance liquibaseForNamedDatasource; + + @Test + @DisplayName("If there is no config for a named datasource, the application should boot, but Liquibase should be deactivated for that datasource") + public void testBootSucceedsButLiquibaseDeactivated() { + assertThatThrownBy(() -> liquibaseForNamedDatasource.get().getConfiguration()) + .isInstanceOf(UnsatisfiedResolutionException.class) + .hasMessageContaining("No bean found"); + } +} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigEmptyTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigEmptyTest.java deleted file mode 100644 index d051c5fe33ad4..0000000000000 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigEmptyTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.quarkus.liquibase.test; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import jakarta.enterprise.inject.Instance; -import jakarta.enterprise.inject.UnsatisfiedResolutionException; -import jakarta.inject.Inject; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.liquibase.LiquibaseFactory; -import io.quarkus.test.QuarkusUnitTest; - -/** - * Liquibase needs a datasource to work. - * This tests assures, that an error occurs, - * as soon as the default liquibase configuration points to an missing default datasource. - */ -public class LiquibaseExtensionConfigEmptyTest { - - @Inject - Instance liquibase; - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addAsResource("config-empty.properties", "application.properties")); - - @Test - @DisplayName("Injecting (default) liquibase should fail if there is no datasource configured") - public void testLiquibaseNotAvailableWithoutDataSource() { - assertThrows(UnsatisfiedResolutionException.class, () -> liquibase.get().getConfiguration()); - } -} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigMissingNamedDataSourceTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigMissingNamedDataSourceTest.java deleted file mode 100644 index 9f215776eb804..0000000000000 --- a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionConfigMissingNamedDataSourceTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.quarkus.liquibase.test; - -import static org.junit.jupiter.api.Assertions.assertThrows; - -import jakarta.enterprise.inject.Instance; -import jakarta.enterprise.inject.UnsatisfiedResolutionException; -import jakarta.inject.Inject; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.liquibase.LiquibaseDataSource; -import io.quarkus.liquibase.LiquibaseFactory; -import io.quarkus.test.QuarkusUnitTest; - -/** - * Liquibase needs a datasource to work. - * This tests assures, that an error occurs, as soon as a named liquibase configuration points to a missing datasource. - */ -public class LiquibaseExtensionConfigMissingNamedDataSourceTest { - - @Inject - @LiquibaseDataSource("users") - Instance liquibase; - - @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addAsResource("db/changeLog.xml", "db/changeLog.xml") - .addAsResource("config-for-missing-named-datasource.properties", "application.properties")); - - @Test - @DisplayName("Injecting liquibase should fail if the named datasource is missing") - public void testLiquibaseNotAvailableWithoutDataSource() { - assertThrows(UnsatisfiedResolutionException.class, liquibase::get); - } -} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigEmptyTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigEmptyTest.java new file mode 100644 index 0000000000000..bad255b85ccf3 --- /dev/null +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigEmptyTest.java @@ -0,0 +1,34 @@ +package io.quarkus.liquibase.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class LiquibaseExtensionMigrateAtStartDefaultDatasourceConfigEmptyTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource("db/changeLog.xml", "db/changeLog.xml")) + .overrideConfigKey("quarkus.liquibase.migrate-at-start", "true") + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false") + .assertException(t -> assertThat(t).cause().cause() + .hasMessageContainingAll("Unable to find datasource '' for Liquibase", + "Datasource '' is not configured.", + "To solve this, configure datasource ''.", + "Refer to https://quarkus.io/guides/datasource for guidance.")); + + @Test + @DisplayName("If there is no config for the default datasource, and if migrate-at-start is enabled, the application should fail to boot") + public void testBootFails() { + // Should not be reached because boot should fail. + assertTrue(false); + } + +} diff --git a/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigEmptyTest.java b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigEmptyTest.java new file mode 100644 index 0000000000000..3cc31a063195b --- /dev/null +++ b/extensions/liquibase/deployment/src/test/java/io/quarkus/liquibase/test/LiquibaseExtensionMigrateAtStartNamedDatasourceConfigEmptyTest.java @@ -0,0 +1,44 @@ +package io.quarkus.liquibase.test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.UnsatisfiedResolutionException; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.liquibase.LiquibaseDataSource; +import io.quarkus.liquibase.LiquibaseFactory; +import io.quarkus.test.QuarkusUnitTest; + +public class LiquibaseExtensionMigrateAtStartNamedDatasourceConfigEmptyTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource("db/changeLog.xml", "db/changeLog.xml")) + .overrideConfigKey("quarkus.liquibase.users.migrate-at-start", "true") + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false") + // We need this otherwise it's going to be the *default* datasource making everything fail + .overrideConfigKey("quarkus.datasource.db-kind", "h2") + .overrideConfigKey("quarkus.datasource.username", "sa") + .overrideConfigKey("quarkus.datasource.password", "sa") + .overrideConfigKey("quarkus.datasource.jdbc.url", + "jdbc:h2:tcp://localhost/mem:test-quarkus-migrate-at-start;DB_CLOSE_DELAY=-1"); + + @Inject + @LiquibaseDataSource("users") + Instance liquibaseForNamedDatasource; + + @Test + @DisplayName("If there is no config for a named datasource, even if migrate-at-start is enabled, the application should boot, but Liquibase should be deactivated for that datasource") + public void testBootSucceedsButLiquibaseDeactivated() { + assertThatThrownBy(() -> liquibaseForNamedDatasource.get().getConfiguration()) + .isInstanceOf(UnsatisfiedResolutionException.class) + .hasMessageContaining("No bean found"); + } +} diff --git a/extensions/liquibase/deployment/src/test/resources/config-empty.properties b/extensions/liquibase/deployment/src/test/resources/config-empty.properties deleted file mode 100644 index 7484177fc8b23..0000000000000 --- a/extensions/liquibase/deployment/src/test/resources/config-empty.properties +++ /dev/null @@ -1 +0,0 @@ -quarkus.datasource.devservices.enabled=false \ No newline at end of file diff --git a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java index d85785d61e35b..4b045a357417b 100644 --- a/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java +++ b/extensions/liquibase/runtime/src/main/java/io/quarkus/liquibase/runtime/LiquibaseRecorder.java @@ -1,5 +1,6 @@ package io.quarkus.liquibase.runtime; +import java.util.Locale; import java.util.function.Function; import javax.sql.DataSource; @@ -13,6 +14,7 @@ import io.quarkus.arc.InjectableInstance; import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.SyntheticCreationalContext; +import io.quarkus.datasource.common.runtime.DataSourceUtil; import io.quarkus.liquibase.LiquibaseFactory; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; @@ -32,9 +34,16 @@ public Function, LiquibaseFactory> return new Function, LiquibaseFactory>() { @Override public LiquibaseFactory apply(SyntheticCreationalContext context) { - DataSource dataSource = context.getInjectedReference(DataSources.class).getDataSource(dataSourceName); - if (dataSource instanceof UnconfiguredDataSource) { - throw new UnsatisfiedResolutionException("No datasource has been configured"); + DataSource dataSource; + try { + dataSource = context.getInjectedReference(DataSources.class).getDataSource(dataSourceName); + if (dataSource instanceof UnconfiguredDataSource) { + throw DataSourceUtil.dataSourceNotConfigured(dataSourceName); + } + } catch (RuntimeException e) { + throw new UnsatisfiedResolutionException(String.format(Locale.ROOT, + "Unable to find datasource '%s' for Liquibase: %s", + dataSourceName, e.getMessage()), e); } LiquibaseFactoryProducer liquibaseProducer = context.getInjectedReference(LiquibaseFactoryProducer.class); diff --git a/extensions/reactive-db2-client/deployment/src/main/java/io/quarkus/reactive/db2/client/deployment/ReactiveDB2ClientProcessor.java b/extensions/reactive-db2-client/deployment/src/main/java/io/quarkus/reactive/db2/client/deployment/ReactiveDB2ClientProcessor.java index d21106cfd1baf..60187addf5680 100644 --- a/extensions/reactive-db2-client/deployment/src/main/java/io/quarkus/reactive/db2/client/deployment/ReactiveDB2ClientProcessor.java +++ b/extensions/reactive-db2-client/deployment/src/main/java/io/quarkus/reactive/db2/client/deployment/ReactiveDB2ClientProcessor.java @@ -219,9 +219,11 @@ private void createPoolIfDefined(DB2PoolRecorder recorder, ExtendedBeanConfigurator mutinyDB2PoolConfigurator = SyntheticBeanBuildItem .configure(io.vertx.mutiny.db2client.DB2Pool.class) .defaultBean() + .addType(io.vertx.mutiny.sqlclient.Pool.class) .scope(ApplicationScoped.class) .addInjectionPoint(POOL_INJECTION_TYPE, injectionPointAnnotations(dataSourceName)) .createWith(recorder.mutinyDB2Pool(poolFunction)) + .unremovable() .setRuntimeInit(); addQualifiers(mutinyDB2PoolConfigurator, dataSourceName); diff --git a/extensions/reactive-mssql-client/deployment/src/main/java/io/quarkus/reactive/mssql/client/deployment/ReactiveMSSQLClientProcessor.java b/extensions/reactive-mssql-client/deployment/src/main/java/io/quarkus/reactive/mssql/client/deployment/ReactiveMSSQLClientProcessor.java index fc29eb683d158..2707e8aba1424 100644 --- a/extensions/reactive-mssql-client/deployment/src/main/java/io/quarkus/reactive/mssql/client/deployment/ReactiveMSSQLClientProcessor.java +++ b/extensions/reactive-mssql-client/deployment/src/main/java/io/quarkus/reactive/mssql/client/deployment/ReactiveMSSQLClientProcessor.java @@ -218,9 +218,11 @@ private void createPoolIfDefined(MSSQLPoolRecorder recorder, ExtendedBeanConfigurator mutinyMSSQLPoolConfigurator = SyntheticBeanBuildItem .configure(io.vertx.mutiny.mssqlclient.MSSQLPool.class) .defaultBean() + .addType(io.vertx.mutiny.sqlclient.Pool.class) .scope(ApplicationScoped.class) .addInjectionPoint(POOL_INJECTION_TYPE, injectionPointAnnotations(dataSourceName)) .createWith(recorder.mutinyMSSQLPool(poolFunction)) + .unremovable() .setRuntimeInit(); addQualifiers(mutinyMSSQLPoolConfigurator, dataSourceName); diff --git a/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/NoConfigTest.java b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/NoConfigTest.java new file mode 100644 index 0000000000000..16b287f159e63 --- /dev/null +++ b/extensions/reactive-mssql-client/deployment/src/test/java/io/quarkus/reactive/mssql/client/NoConfigTest.java @@ -0,0 +1,145 @@ +package io.quarkus.reactive.mssql.client; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.reactive.datasource.ReactiveDataSource; +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.mssqlclient.MSSQLPool; +import io.vertx.sqlclient.Pool; + +/** + * We should be able to start the application, even with no configuration at all. + */ +public class NoConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + private static final Duration MAX_WAIT = Duration.ofSeconds(10); + + @Inject + MyBean myBean; + + @Test + public void pool_default() { + Pool pool = Arc.container().instance(Pool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().toCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void mutinyPool_default() { + io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().subscribeAsCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void vendorPool_default() { + MSSQLPool pool = Arc.container().instance(MSSQLPool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().toCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void mutinyVendorPool_default() { + io.vertx.mutiny.mssqlclient.MSSQLPool pool = Arc.container().instance(io.vertx.mutiny.mssqlclient.MSSQLPool.class) + .get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().subscribeAsCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void pool_named() { + Pool pool = Arc.container().instance(Pool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void mutinyPool_named() { + io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void vendorPool_named() { + MSSQLPool pool = Arc.container().instance(MSSQLPool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void mutinyVendorPool_named() { + io.vertx.mutiny.mssqlclient.MSSQLPool pool = Arc.container().instance(io.vertx.mutiny.mssqlclient.MSSQLPool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void injectedBean_default() { + assertThat(myBean.usePool()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @ApplicationScoped + public static class MyBean { + @Inject + MSSQLPool pool; + + public CompletionStage usePool() { + return pool.getConnection().toCompletionStage(); + } + } +} diff --git a/extensions/reactive-mysql-client/deployment/src/main/java/io/quarkus/reactive/mysql/client/deployment/ReactiveMySQLClientProcessor.java b/extensions/reactive-mysql-client/deployment/src/main/java/io/quarkus/reactive/mysql/client/deployment/ReactiveMySQLClientProcessor.java index 7f61ab1eb9231..4bb36e3156dd0 100644 --- a/extensions/reactive-mysql-client/deployment/src/main/java/io/quarkus/reactive/mysql/client/deployment/ReactiveMySQLClientProcessor.java +++ b/extensions/reactive-mysql-client/deployment/src/main/java/io/quarkus/reactive/mysql/client/deployment/ReactiveMySQLClientProcessor.java @@ -219,9 +219,11 @@ private void createPoolIfDefined(MySQLPoolRecorder recorder, ExtendedBeanConfigurator mutinyMySQLPoolConfigurator = SyntheticBeanBuildItem .configure(io.vertx.mutiny.mysqlclient.MySQLPool.class) .defaultBean() + .addType(io.vertx.mutiny.sqlclient.Pool.class) .scope(ApplicationScoped.class) .addInjectionPoint(POOL_INJECTION_TYPE, injectionPointAnnotations(dataSourceName)) .createWith(recorder.mutinyMySQLPool(poolFunction)) + .unremovable() .setRuntimeInit(); addQualifiers(mutinyMySQLPoolConfigurator, dataSourceName); diff --git a/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/NoConfigTest.java b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/NoConfigTest.java new file mode 100644 index 0000000000000..ea98c0acb5e8b --- /dev/null +++ b/extensions/reactive-mysql-client/deployment/src/test/java/io/quarkus/reactive/mysql/client/NoConfigTest.java @@ -0,0 +1,145 @@ +package io.quarkus.reactive.mysql.client; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.reactive.datasource.ReactiveDataSource; +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.mysqlclient.MySQLPool; +import io.vertx.sqlclient.Pool; + +/** + * We should be able to start the application, even with no configuration at all. + */ +public class NoConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + private static final Duration MAX_WAIT = Duration.ofSeconds(10); + + @Inject + MyBean myBean; + + @Test + public void pool_default() { + Pool pool = Arc.container().instance(Pool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().toCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void mutinyPool_default() { + io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().subscribeAsCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void vendorPool_default() { + MySQLPool pool = Arc.container().instance(MySQLPool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().toCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void mutinyVendorPool_default() { + io.vertx.mutiny.mysqlclient.MySQLPool pool = Arc.container().instance(io.vertx.mutiny.mysqlclient.MySQLPool.class) + .get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().subscribeAsCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void pool_named() { + Pool pool = Arc.container().instance(Pool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void mutinyPool_named() { + io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void vendorPool_named() { + MySQLPool pool = Arc.container().instance(MySQLPool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void mutinyVendorPool_named() { + io.vertx.mutiny.mysqlclient.MySQLPool pool = Arc.container().instance(io.vertx.mutiny.mysqlclient.MySQLPool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void injectedBean_default() { + assertThat(myBean.usePool()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @ApplicationScoped + public static class MyBean { + @Inject + MySQLPool pool; + + public CompletionStage usePool() { + return pool.getConnection().toCompletionStage(); + } + } +} diff --git a/extensions/reactive-oracle-client/deployment/src/main/java/io/quarkus/reactive/oracle/client/deployment/ReactiveOracleClientProcessor.java b/extensions/reactive-oracle-client/deployment/src/main/java/io/quarkus/reactive/oracle/client/deployment/ReactiveOracleClientProcessor.java index a82812d3d8409..ab1cf2dceff79 100644 --- a/extensions/reactive-oracle-client/deployment/src/main/java/io/quarkus/reactive/oracle/client/deployment/ReactiveOracleClientProcessor.java +++ b/extensions/reactive-oracle-client/deployment/src/main/java/io/quarkus/reactive/oracle/client/deployment/ReactiveOracleClientProcessor.java @@ -220,9 +220,11 @@ private void createPoolIfDefined(OraclePoolRecorder recorder, ExtendedBeanConfigurator mutinyOraclePoolConfigurator = SyntheticBeanBuildItem .configure(io.vertx.mutiny.oracleclient.OraclePool.class) .defaultBean() + .addType(io.vertx.mutiny.sqlclient.Pool.class) .scope(ApplicationScoped.class) .addInjectionPoint(POOL_INJECTION_TYPE, injectionPointAnnotations(dataSourceName)) .createWith(recorder.mutinyOraclePool(poolFunction)) + .unremovable() .setRuntimeInit(); addQualifiers(mutinyOraclePoolConfigurator, dataSourceName); diff --git a/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/NoConfigTest.java b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/NoConfigTest.java new file mode 100644 index 0000000000000..7b2899780263f --- /dev/null +++ b/extensions/reactive-oracle-client/deployment/src/test/java/io/quarkus/reactive/oracle/client/NoConfigTest.java @@ -0,0 +1,145 @@ +package io.quarkus.reactive.oracle.client; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.reactive.datasource.ReactiveDataSource; +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.oracleclient.OraclePool; +import io.vertx.sqlclient.Pool; + +/** + * We should be able to start the application, even with no configuration at all. + */ +public class NoConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + private static final Duration MAX_WAIT = Duration.ofSeconds(10); + + @Inject + MyBean myBean; + + @Test + public void pool_default() { + Pool pool = Arc.container().instance(Pool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().toCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Cannot connect"); + } + + @Test + public void mutinyPool_default() { + io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().subscribeAsCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Cannot connect"); + } + + @Test + public void vendorPool_default() { + OraclePool pool = Arc.container().instance(OraclePool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().toCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Cannot connect"); + } + + @Test + public void mutinyVendorPool_default() { + io.vertx.mutiny.oracleclient.OraclePool pool = Arc.container().instance(io.vertx.mutiny.oracleclient.OraclePool.class) + .get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().subscribeAsCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Cannot connect"); + } + + @Test + public void pool_named() { + Pool pool = Arc.container().instance(Pool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void mutinyPool_named() { + io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void vendorPool_named() { + OraclePool pool = Arc.container().instance(OraclePool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void mutinyVendorPool_named() { + io.vertx.mutiny.oracleclient.OraclePool pool = Arc.container().instance(io.vertx.mutiny.oracleclient.OraclePool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void injectedBean_default() { + assertThat(myBean.usePool()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Cannot connect"); + } + + @ApplicationScoped + public static class MyBean { + @Inject + OraclePool pool; + + public CompletionStage usePool() { + return pool.getConnection().toCompletionStage(); + } + } +} diff --git a/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java b/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java index e1db7a21692b0..cce55cfa31a5c 100644 --- a/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java +++ b/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java @@ -224,9 +224,11 @@ private void createPoolIfDefined(PgPoolRecorder recorder, ExtendedBeanConfigurator mutinyPgPoolConfigurator = SyntheticBeanBuildItem .configure(io.vertx.mutiny.pgclient.PgPool.class) .defaultBean() + .addType(io.vertx.mutiny.sqlclient.Pool.class) .scope(ApplicationScoped.class) .addInjectionPoint(POOL_INJECTION_TYPE, injectionPointAnnotations(dataSourceName)) .createWith(recorder.mutinyPgPool(poolFunction)) + .unremovable() .setRuntimeInit(); addQualifiers(mutinyPgPoolConfigurator, dataSourceName); diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/NoConfigTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/NoConfigTest.java new file mode 100644 index 0000000000000..ceaa86c73563f --- /dev/null +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/NoConfigTest.java @@ -0,0 +1,144 @@ +package io.quarkus.reactive.pg.client; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Arc; +import io.quarkus.reactive.datasource.ReactiveDataSource; +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.pgclient.PgPool; +import io.vertx.sqlclient.Pool; + +/** + * We should be able to start the application, even with no configuration at all. + */ +public class NoConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + // The datasource won't be truly "unconfigured" if dev services are enabled + .overrideConfigKey("quarkus.devservices.enabled", "false"); + + private static final Duration MAX_WAIT = Duration.ofSeconds(10); + + @Inject + MyBean myBean; + + @Test + public void pool_default() { + Pool pool = Arc.container().instance(Pool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().toCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void mutinyPool_default() { + io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().subscribeAsCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void vendorPool_default() { + PgPool pool = Arc.container().instance(PgPool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().toCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void mutinyVendorPool_default() { + io.vertx.mutiny.pgclient.PgPool pool = Arc.container().instance(io.vertx.mutiny.pgclient.PgPool.class).get(); + + // The default datasource is a bit special; + // it's historically always been considered as "present" even if there was no explicit configuration. + // So the bean will never be null. + assertThat(pool).isNotNull(); + // However, if unconfigured, it will use default connection config (host, port, username, ...) and will fail. + assertThat(pool.getConnection().subscribeAsCompletionStage()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @Test + public void pool_named() { + Pool pool = Arc.container().instance(Pool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void mutinyPool_named() { + io.vertx.mutiny.sqlclient.Pool pool = Arc.container().instance(io.vertx.mutiny.sqlclient.Pool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void vendorPool_named() { + PgPool pool = Arc.container().instance(PgPool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void mutinyVendorPool_named() { + io.vertx.mutiny.pgclient.PgPool pool = Arc.container().instance(io.vertx.mutiny.pgclient.PgPool.class, + new ReactiveDataSource.ReactiveDataSourceLiteral("ds-1")).get(); + // An unconfigured, named datasource has no corresponding bean. + assertThat(pool).isNull(); + } + + @Test + public void injectedBean_default() { + assertThat(myBean.usePool()) + .failsWithin(MAX_WAIT) + .withThrowableThat() + .withMessageContaining("Connection refused"); + } + + @ApplicationScoped + public static class MyBean { + @Inject + PgPool pool; + + public CompletionStage usePool() { + return pool.getConnection().toCompletionStage(); + } + } +}