HHH-18602 Expose `determineDatabaseVersion` in `Dialect`

This commit is contained in:
Yoann Rodière 2024-09-11 19:28:18 +02:00 committed by Steve Ebersole
parent 4bd164222f
commit 4b33d0d067
8 changed files with 177 additions and 38 deletions

View File

@ -344,7 +344,7 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
}
protected Dialect(DialectResolutionInfo info) {
this.version = info.makeCopyOrDefault( getMinimumSupportedVersion() );
this.version = determineDatabaseVersion( info );
checkVersion();
registerDefaultKeywords();
registerKeywords(info);
@ -363,6 +363,17 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun
}
}
/**
* Determine the database version, as precise as possible and using Dialect-specific techniques,
* from a {@link DialectResolutionInfo} object.
* @param info The dialect resolution info that would be passed by Hibernate ORM
* to the constructor of a Dialect of the same type.
* @return The corresponding database version.
*/
public DatabaseVersion determineDatabaseVersion(DialectResolutionInfo info) {
return info.makeCopyOrDefault( getMinimumSupportedVersion() );
}
/**
* Set appropriate default values for configuration properties.
* <p>

View File

@ -126,7 +126,7 @@ public class H2Dialect extends Dialect {
private final OptionalTableUpdateStrategy optionalTableUpdateStrategy;
public H2Dialect(DialectResolutionInfo info) {
this( parseVersion( info ) );
this( staticDetermineDatabaseVersion( info ) );
registerKeywords( info );
}
@ -151,7 +151,13 @@ public class H2Dialect extends Dialect {
this.optionalTableUpdateStrategy = H2Dialect::usingMerge;
}
private static DatabaseVersion parseVersion(DialectResolutionInfo info) {
@Override
public DatabaseVersion determineDatabaseVersion(DialectResolutionInfo info) {
return staticDetermineDatabaseVersion(info);
}
// Static version necessary to call from constructor
private static DatabaseVersion staticDetermineDatabaseVersion(DialectResolutionInfo info) {
DatabaseVersion version = info.makeCopyOrDefault( MINIMUM_VERSION );
if ( info.getDatabaseVersion() != null ) {
version = DatabaseVersion.make( version.getMajor(), version.getMinor(), parseBuildId( info ) );

View File

@ -202,6 +202,10 @@ public class HANADialect extends Dialect {
return MINIMUM_VERSION;
}
@Override
public DatabaseVersion determineDatabaseVersion(DialectResolutionInfo info) {
return HANAServerConfiguration.staticDetermineDatabaseVersion( info );
}
// Use column or row tables by default
public static final String USE_DEFAULT_TABLE_TYPE_COLUMN = "hibernate.dialect.hana.use_default_table_type_column";

View File

@ -75,10 +75,10 @@ public class HANAServerConfiguration {
MAX_LOB_PREFETCH_SIZE_DEFAULT_VALUE
);
}
return new HANAServerConfiguration( createVersion( info ), maxLobPrefetchSize );
return new HANAServerConfiguration( staticDetermineDatabaseVersion( info ), maxLobPrefetchSize );
}
private static DatabaseVersion createVersion(DialectResolutionInfo info) {
static DatabaseVersion staticDetermineDatabaseVersion(DialectResolutionInfo info) {
// Parse the version according to https://answers.sap.com/questions/9760991/hana-sps-version-check.html
final String versionString = info.getDatabaseVersion();
int majorVersion = 1;

View File

@ -174,11 +174,17 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
}
public SQLServerDialect(DialectResolutionInfo info) {
this( determineDatabaseVersion( info ) );
this( staticDetermineDatabaseVersion( info ) );
registerKeywords( info );
}
private static DatabaseVersion determineDatabaseVersion(DialectResolutionInfo info) {
@Override
public DatabaseVersion determineDatabaseVersion(DialectResolutionInfo info) {
return staticDetermineDatabaseVersion(info);
}
// Static version necessary to call from constructor
private static DatabaseVersion staticDetermineDatabaseVersion(DialectResolutionInfo info) {
final Integer compatibilityLevel = getCompatibilityLevel( info );
if ( compatibilityLevel != null ) {
final int majorVersion = compatibilityLevel / 10;

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.dialect;
import java.util.Objects;
/**
* Simple version of DatabaseVersion
*/
@ -101,4 +103,21 @@ public class SimpleDatabaseVersion implements DatabaseVersion {
}
return version.toString();
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
SimpleDatabaseVersion that = (SimpleDatabaseVersion) o;
return major == that.major && minor == that.minor && micro == that.micro;
}
@Override
public int hashCode() {
return Objects.hash( major, minor, micro );
}
}

View File

@ -12,9 +12,11 @@ import static org.junit.jupiter.api.Assertions.fail;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.stream.Stream;
import org.hibernate.HibernateException;
import org.hibernate.boot.registry.StandardServiceInitiator;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
@ -33,12 +35,18 @@ import org.hibernate.dialect.PostgresPlusDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SpannerDialect;
import org.hibernate.dialect.SybaseDialect;
import org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl;
import org.hibernate.engine.jdbc.dialect.spi.DialectFactory;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfoSource;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.service.spi.ServiceException;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.hibernate.testing.env.TestingDatabaseInfo;
import org.hibernate.testing.logger.Triggerable;
import org.hibernate.testing.orm.junit.DialectContext;
import org.hibernate.testing.orm.junit.Jira;
import org.hibernate.testing.orm.logger.LoggerInspectionExtension;
import org.junit.jupiter.api.BeforeEach;
@ -89,8 +97,7 @@ public class MetadataAccessTests {
.doesNotContainKeys( JdbcSettings.DIALECT, JdbcSettings.JAKARTA_HBM2DDL_DB_NAME );
try (StandardServiceRegistry registry = registryBuilder.build()) {
final JdbcEnvironment jdbcEnvironment = registry.getService( JdbcEnvironment.class );
final Dialect dialect = jdbcEnvironment.getDialect();
final Dialect dialect = getDialect( registry );
assertThat( dialect ).isNotNull();
assertThat( dialect ).isInstanceOf( H2Dialect.class );
}
@ -134,7 +141,20 @@ public class MetadataAccessTests {
@ParameterizedTest
@MethodSource("dialects")
void testAccessDisabledExplicitDialect(String productName, Class<?> dialectClass, DatabaseVersion expectedDatabaseVersion) {
void testAccessDisabledExplicitDialect(String productName, Class<?> dialectClass,
DatabaseVersion expectedDatabaseVersion) {
try ( StandardServiceRegistry registry = createRegistryWithMetadataAccessDisabledAndDialect( dialectClass ) ) {
final Dialect dialect = getDialect( registry );
assertThat( dialect ).isInstanceOf( dialectClass );
assertThat( dialect.getVersion() ).isEqualTo( expectedDatabaseVersion );
}
assertThat( triggerable.triggerMessages() )
.as( triggerable.toString() )
.isEmpty();
}
private StandardServiceRegistry createRegistryWithMetadataAccessDisabledAndDialect(Class<?> dialectClass) {
final StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder();
registryBuilder.clearSettings();
@ -143,16 +163,7 @@ public class MetadataAccessTests {
assertThat( registryBuilder.getSettings() )
.doesNotContainKeys( JdbcSettings.JAKARTA_HBM2DDL_DB_NAME );
try (StandardServiceRegistry registry = registryBuilder.build()) {
final JdbcEnvironment jdbcEnvironment = registry.getService( JdbcEnvironment.class );
final Dialect dialect = jdbcEnvironment.getDialect();
assertThat( dialect ).isInstanceOf( dialectClass );
assertThat( dialect.getVersion() ).isEqualTo( expectedDatabaseVersion );
}
assertThat( triggerable.triggerMessages() )
.as( triggerable.toString() )
.isEmpty();
return registryBuilder.build();
}
@ParameterizedTest
@ -190,8 +201,7 @@ public class MetadataAccessTests {
registryBuilder.applySetting( JdbcSettings.ALLOW_METADATA_ON_BOOT, false );
try (StandardServiceRegistry registry = registryBuilder.build()) {
final JdbcEnvironment jdbcEnvironment = registry.getService( JdbcEnvironment.class );
final Dialect dialect = jdbcEnvironment.getDialect();
final Dialect dialect = getDialect( registry );
fail( "Should fail to boot - " + dialect );
}
catch (ServiceException expected) {
@ -201,6 +211,50 @@ public class MetadataAccessTests {
}
}
@Test
void testDetermineDatabaseVersion() {
final Dialect metadataAccessDisabledDialect;
try ( StandardServiceRegistry registry =
createRegistryWithMetadataAccessDisabledAndDialect( DialectContext.getDialectClass() ) ) {
// The version on this dialect may be anything, but most likely will be the minimum version.
// We're not interested in that, but in how determineDatabaseVersion() behaves for this dialect,
// when passed actual resolution info -- which Quarkus may do.
metadataAccessDisabledDialect = getDialect( registry );
}
try ( StandardServiceRegistry registry = createRegistryWithTestedDatabaseAndMetadataAccessAllowed() ) {
final Dialect metadataAccessAllowedDialect = getDialect( registry );
// We expect determineDatabaseVersion(), when called on metadataAccessDisabledDialect,
// to return the version that would have been returned,
// had we booted up with auto-detection of version (metadata access allowed).
assertThat( metadataAccessDisabledDialect.determineDatabaseVersion( getDialectResolutionInfo( registry ) ) )
.isEqualTo( metadataAccessAllowedDialect.getVersion() );
}
}
private StandardServiceRegistry createRegistryWithTestedDatabaseAndMetadataAccessAllowed() {
final StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder();
registryBuilder.addInitiator( new CapturingDialectFactory.Initiator() );
// allow access to the jdbc metadata
registryBuilder.applySetting( JdbcSettings.ALLOW_METADATA_ON_BOOT, true );
// leave connection info as defined in global test configuration (most likely system properties)
return registryBuilder.build();
}
private static Dialect getDialect(StandardServiceRegistry registry) {
return registry.getService( JdbcEnvironment.class ).getDialect();
}
private static DialectResolutionInfo getDialectResolutionInfo(StandardServiceRegistry registry) {
return ( (CapturingDialectFactory) registry.getService( DialectFactory.class ) )
.capturedDialectResolutionInfoSource.getDialectResolutionInfo();
}
// Ugly hack because neither MINIMUM_VERSION nor getMinimumSupportedVersion()
// can be accessed from this test.
private static DatabaseVersion getVersionConstant(Class<? extends Dialect> dialectClass, String versionConstantName) {
@ -213,4 +267,29 @@ public class MetadataAccessTests {
throw new RuntimeException( "Error extracting '" + versionConstantName + "' from '" + dialectClass + "'", e );
}
}
// A hack to easily retrieve DialectResolutionInfo exactly as it would be constructed by Hibernate ORM
private static class CapturingDialectFactory extends DialectFactoryImpl {
static class Initiator implements StandardServiceInitiator<DialectFactory> {
@Override
public Class<DialectFactory> getServiceInitiated() {
return DialectFactory.class;
}
@Override
public DialectFactory initiateService(Map<String, Object> configurationValues,
ServiceRegistryImplementor registry) {
return new CapturingDialectFactory();
}
}
DialectResolutionInfoSource capturedDialectResolutionInfoSource;
@Override
public Dialect buildDialect(Map<String, Object> configValues, DialectResolutionInfoSource resolutionInfoSource)
throws HibernateException {
this.capturedDialectResolutionInfoSource = resolutionInfoSource;
return super.buildDialect( configValues, resolutionInfoSource );
}
}
}

View File

@ -25,40 +25,47 @@ import org.hibernate.internal.util.ReflectHelper;
*/
public final class DialectContext {
private static Class<? extends Dialect> dialectClass;
private static Dialect dialect;
static void init() {
static void initDialectClass() {
final Properties properties = Environment.getProperties();
final String diverClassName = properties.getProperty( Environment.DRIVER );
final String dialectName = properties.getProperty( Environment.DIALECT );
final String jdbcUrl = properties.getProperty( Environment.URL );
final Properties props = new Properties();
props.setProperty( "user", properties.getProperty( Environment.USER ) );
props.setProperty( "password", properties.getProperty( Environment.PASS ) );
if ( dialectName == null ) {
throw new HibernateException( "The dialect was not set. Set the property hibernate.dialect." );
}
final Constructor<? extends Dialect> constructor;
try {
@SuppressWarnings("unchecked")
final Class<? extends Dialect> dialectClass = (Class<? extends Dialect>) ReflectHelper.classForName( dialectName );
constructor = dialectClass.getConstructor( DialectResolutionInfo.class );
dialectClass = (Class<? extends Dialect>) ReflectHelper.classForName( dialectName );
}
catch (ClassNotFoundException cnfe) {
throw new HibernateException( "Dialect class not found: " + dialectName, cnfe );
}
}
static void init() {
final Properties properties = Environment.getProperties();
final String driverClassName = properties.getProperty( Environment.DRIVER );
final String jdbcUrl = properties.getProperty( Environment.URL );
final Properties props = new Properties();
props.setProperty( "user", properties.getProperty( Environment.USER ) );
props.setProperty( "password", properties.getProperty( Environment.PASS ) );
final Class<? extends Dialect> dialectClass = getDialectClass();
final Constructor<? extends Dialect> constructor;
try {
constructor = dialectClass.getConstructor( DialectResolutionInfo.class );
}
catch (Exception e) {
throw new HibernateException( "Could not instantiate given dialect class: " + dialectName, e );
throw new HibernateException( "Could not instantiate given dialect class: " + dialectClass, e );
}
final Driver driver;
try {
driver = (Driver) Class.forName( diverClassName ).newInstance();
driver = (Driver) Class.forName( driverClassName ).newInstance();
}
catch (ClassNotFoundException cnfe) {
throw new HibernateException( "JDBC Driver class not found: " + dialectName, cnfe );
throw new HibernateException( "JDBC Driver class not found: " + driverClassName, cnfe );
}
catch (Exception e) {
throw new HibernateException( "Could not instantiate given JDBC driver class: " + dialectName, e );
throw new HibernateException( "Could not instantiate given JDBC driver class: " + driverClassName, e );
}
try ( Connection connection = driver.connect( jdbcUrl, props ) ) {
// if ( jdbcUrl.startsWith( "jdbc:derby:" ) ) {
@ -77,13 +84,20 @@ public final class DialectContext {
+ jdbcUrl + "' [" + sqle.getMessage() + "]", sqle );
}
catch (Exception e) {
throw new HibernateException( "Could not instantiate given dialect class: " + dialectName, e );
throw new HibernateException( "Could not connect to database with dialect class: " + dialectClass, e );
}
}
private DialectContext() {
}
public static synchronized Class<? extends Dialect> getDialectClass() {
if ( dialectClass == null ) {
initDialectClass();
}
return dialectClass;
}
public static synchronized Dialect getDialect() {
if (dialect == null) {
init();