-
Notifications
You must be signed in to change notification settings - Fork 13
Make sure our extension does not interfere when bootstrapping is unrelated to it #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test classes should have the "Tests" suffix. This one had, but the name of the file in which it was declared was "Test".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file was renamed to NativeBootstrappingIntegrationTests. The code was also simplified, taking into account that MongoExtension now disables fail points.
| var item = new Item(); | ||
| item.id = 1; | ||
| em.persist(item); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only drive-by changes here to make the test code simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test classes should have the "Tests" suffix.
| @Override | ||
| public void contribute( | ||
| AdditionalMappingContributions contributions, | ||
| InFlightMetadataCollector metadata, | ||
| ResourceStreamLocator resourceStreamLocator, | ||
| MetadataBuildingContext buildingContext) { | ||
| if (!(metadata.getDatabase().getDialect() instanceof MongoDialect)) { | ||
| // avoid interfering with bootstrapping not related to the MongoDB Extension for Hibernate ORM | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the actual fix.
| @@ -14,7 +14,7 @@ | |||
| * limitations under the License. | |||
| */ | |||
|
|
|||
| package com.mongodb.hibernate.internal.extension.service; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the program elements we have represent part of the extension. The extension in the package name does not make sense, though originally I thought it would make sense.
| private @Nullable StandardServiceRegistryScopedState standardServiceRegistryScopedState; | ||
| private transient @Nullable MongoClient mongoClient; | ||
|
|
||
| public MongoConnectionProvider() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made the constructor used by Hibernate ORM explicit, like we do in other places.
| */ | ||
|
|
||
| package com.mongodb.hibernate.internal.extension; | ||
| package com.mongodb.hibernate.internal.boot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Most of the program elements we have represent part of the extension. The
extensionin the package name does not make sense, though originally I thought it would make sense. org.hibernate.boot.spi.AdditionalMappingContributoris in thebootpackage, which is why I putMongoAdditionalMappingContributorto thebootpackage.
c2269ef to
76835a8
Compare
…ed to it HIBERNATE-135
76835a8 to
9db8582
Compare
src/integrationTest/java/com/mongodb/hibernate/boot/NativeBootstrappingIntegrationTests.java
Outdated
Show resolved
Hide resolved
HIBERNATE-135
|
As discussed offline, we could use also check if dialect is MongoDB dialect in our |
…der is not plugged in HIBERNATE-135
Done in f7dbe58. |
| * MongoDB Extension for Hibernate ORM. | ||
| */ | ||
| @Test | ||
| void testMongoAdditionalMappingContributorIsSkipped() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new test fails without the changes done to MongoAdditionalMappingContributor.contribute.
Previously in this PR this test was in PostgreSQLBootstrappingIntegrationTests, but I realized that it can be made a unit test without any dependency on the PostgreSQL driver.
|
|
||
| class NativeBootstrappingTests { | ||
| @Test | ||
| void testMongoDialectNotPluggedIn() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note how there is no testMongoConnectionProviderNotPluggedIn test in NativeBootstrappingTests. That is because it's impossible to write such a test without actually connecting to a DBMS, and that DBMS would have to be not MongoDB, as the connection provider cannot be ours.
However, StandardServiceRegistryScopedStateTests has both testMongoDialectNotPluggedIn and testMongoConnectionProviderNotPluggedIn.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One possible way to simulate this failure path without connecting to a real DBMS is to inject a mocked ConnectionProvider in this case, along with a mocked Connection. The bootstrap will still fail at AbstractMqlTranslator.<init>(AbstractMqlTranslator.java:220) because it depends on StandardServiceRegistryScopedState, which is the expected behavior.
Example
@Test
void test() throws SQLException {
var standardServiceRegistryBuilder =
new StandardServiceRegistryBuilder(new BootstrapServiceRegistryBuilder().build());
standardServiceRegistryBuilder.clearSettings();
ConnectionProvider connectionProvider =
mock(ConnectionProvider.class, Mockito.RETURNS_SMART_NULLS);
Connection connection =
mock(Connection.class, Mockito.RETURNS_SMART_NULLS);
when(connectionProvider.getConnection()).thenReturn(connection);
assertThatThrownBy(() ->
new MetadataSources()
.addAnnotatedClass(Item.class)
.buildMetadata(
standardServiceRegistryBuilder
.applySetting(DIALECT, "com.mongodb.hibernate.dialect.MongoDialect")
.applySetting(JAKARTA_JDBC_URL, "mongodb://localhost/mongo-hibernate-test?directConnection=false")
.applySetting(CONNECTION_PROVIDER, connectionProvider)
.applySetting(ALLOW_METADATA_ON_BOOT, false)
.build()
)
.buildSessionFactory()
.close()
).hasRootCauseMessage(
"com.mongodb.hibernate.jdbc.MongoConnectionProvider must be plugged in, " +
"for example, via the [hibernate.connection.provider_class] configuration property"
);
}
This doesn’t reproduce the full end-to-end scenario, but it might be sufficient to exercise the “connection provider not plugged in” branch when provider is not ours.
| .buildMetadata(new StandardServiceRegistryBuilder() | ||
| .applySettings(settings) | ||
| .build()) | ||
| .buildSessionFactory(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A drive-by simplification.
| Map.of(JAKARTA_JDBC_URL, "jdbc:postgresql://localhost/test")) | ||
| .close()); | ||
| assertThrows( | ||
| ServiceException.class, () -> buildSessionFactory(Map.of(JAKARTA_JDBC_URL, "jdbc:postgresql://host/")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the ULR such that there is obviously no chance in practice to successfully connect using it.
| public StandardServiceRegistryScopedState initiateService( | ||
| Map<String, Object> configurationValues, ServiceRegistryImplementor serviceRegistry) { | ||
| checkMongoDialectIsPluggedIn(configurationValues, serviceRegistry); | ||
| checkMongoConnectionProviderIsPluggedIn(configurationValues); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally I wanted to put the checkMongoConnectionProviderIsPluggedIn check into MongoDialect. Unfortunately, there is no way, as far as I can see, to check configuration properties from MongoDialect. The closest to the dialect place where we can do that is the constructor of AbstractMqlTranslator. However, that constructor requires StandardServiceRegistryScopedState, which is why I put the checkMongoConnectionProviderIsPluggedIn check here, close to the checkMongoDialectIsPluggedIn check, which should be here anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One possible option could be to require a MongoConnectionProvider service in the contribute method of MongoDialect, so that bootstrap fails if MongoConnectionProvider is not present. However, this is not an ideal place for this validation either.
Given that, i agree that AbstractMqlTranslator requiring StandardServiceRegistryScopedState seems sufficient.
| return new MetadataSources() | ||
| .buildMetadata(new StandardServiceRegistryBuilder() | ||
| .addService(MongoConfigurationContributor.class, mongoConfigurationContributor) | ||
| .build()) | ||
| .getMetadataBuilder() | ||
| .build() | ||
| .getSessionFactoryBuilder() | ||
| .build(); | ||
| .buildSessionFactory(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A drive-by simplification.
HIBERNATE-135
| || (dialect instanceof Class<?> dialectClass | ||
| && MongoDialect.class.isAssignableFrom(dialectClass)) | ||
| || (dialect instanceof String dialectName | ||
| && dialectName.startsWith("com.mongodb.hibernate")))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use an exact name match here, given that we currently support only a single dialect?
| && dialectName.startsWith("com.mongodb.hibernate")))) { | |
| && dialectName.equals(MongoDialect.class.getName())))) { |
|
|
||
| class NativeBootstrappingTests { | ||
| @Test | ||
| void testMongoDialectNotPluggedIn() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One possible way to simulate this failure path without connecting to a real DBMS is to inject a mocked ConnectionProvider in this case, along with a mocked Connection. The bootstrap will still fail at AbstractMqlTranslator.<init>(AbstractMqlTranslator.java:220) because it depends on StandardServiceRegistryScopedState, which is the expected behavior.
Example
@Test
void test() throws SQLException {
var standardServiceRegistryBuilder =
new StandardServiceRegistryBuilder(new BootstrapServiceRegistryBuilder().build());
standardServiceRegistryBuilder.clearSettings();
ConnectionProvider connectionProvider =
mock(ConnectionProvider.class, Mockito.RETURNS_SMART_NULLS);
Connection connection =
mock(Connection.class, Mockito.RETURNS_SMART_NULLS);
when(connectionProvider.getConnection()).thenReturn(connection);
assertThatThrownBy(() ->
new MetadataSources()
.addAnnotatedClass(Item.class)
.buildMetadata(
standardServiceRegistryBuilder
.applySetting(DIALECT, "com.mongodb.hibernate.dialect.MongoDialect")
.applySetting(JAKARTA_JDBC_URL, "mongodb://localhost/mongo-hibernate-test?directConnection=false")
.applySetting(CONNECTION_PROVIDER, connectionProvider)
.applySetting(ALLOW_METADATA_ON_BOOT, false)
.build()
)
.buildSessionFactory()
.close()
).hasRootCauseMessage(
"com.mongodb.hibernate.jdbc.MongoConnectionProvider must be plugged in, " +
"for example, via the [hibernate.connection.provider_class] configuration property"
);
}
This doesn’t reproduce the full end-to-end scenario, but it might be sufficient to exercise the “connection provider not plugged in” branch when provider is not ours.
| var dialect = configurationValues.get(AvailableSettings.DIALECT); | ||
| if (!((dialect instanceof MongoDialect) | ||
| || (dialect instanceof Class<?> dialectClass | ||
| && MongoDialect.class.isAssignableFrom(dialectClass)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use equals given that MongoDialect is final?
HIBERNATE-135