From 1d76f970e80686372bb4e406df95ab7bb2bf6b3f Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Thu, 15 Sep 2022 00:27:10 +0200 Subject: [PATCH] HHH-15511 - fix version determination for CockroachDB Signed-off-by: Jan Schatteman --- .../hibernate/dialect/CockroachDialect.java | 54 +++++++- .../hibernate/internal/CoreMessageLogger.java | 4 + .../unit/CockroachDialectVersionTest.java | 125 ++++++++++++++++++ 3 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/CockroachDialectVersionTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java index 0dad42b95b..e293128482 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -7,6 +7,7 @@ package org.hibernate.dialect; import java.sql.DatabaseMetaData; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.time.temporal.TemporalAccessor; @@ -14,6 +15,8 @@ import java.util.Calendar; import java.util.Date; import java.util.Map; import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import jakarta.persistence.TemporalType; @@ -34,6 +37,7 @@ import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.CoreMessageLogger; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.spi.QueryEngine; @@ -63,6 +67,8 @@ import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; import org.hibernate.type.descriptor.sql.internal.Scale6IntervalSecondDdlType; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; +import org.jboss.logging.Logger; + import static org.hibernate.query.sqm.TemporalUnit.DAY; import static org.hibernate.query.sqm.TemporalUnit.NATIVE; import static org.hibernate.type.SqlTypes.BINARY; @@ -97,10 +103,14 @@ import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTimestampWithM */ public class CockroachDialect extends Dialect { + private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, CockroachDialect.class.getName() ); private static final CockroachDBIdentityColumnSupport IDENTITY_COLUMN_SUPPORT = new CockroachDBIdentityColumnSupport(); // KNOWN LIMITATIONS: // * no support for java.sql.Clob + // Pre-compile and reuse pattern + private static final Pattern CRDB_VERSION_PATTERN = Pattern.compile( "v[\\d]+(\\.[\\d]+)?(\\.[\\d]+)?" ); + private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 21, 1 ); private final PostgreSQLDriverKind driverKind; @@ -110,8 +120,8 @@ public class CockroachDialect extends Dialect { } public CockroachDialect(DialectResolutionInfo info) { - super(info); - driverKind = PostgreSQLDriverKind.determineKind( info ); + this( fetchDataBaseVersion( info ), PostgreSQLDriverKind.determineKind( info ) ); + registerKeywords( info ); } public CockroachDialect(DatabaseVersion version) { @@ -124,6 +134,46 @@ public class CockroachDialect extends Dialect { this.driverKind = driverKind; } + protected static DatabaseVersion fetchDataBaseVersion( DialectResolutionInfo info ) { + String versionString = null; + if ( info.getDatabaseMetadata() != null ) { + try (java.sql.Statement s = info.getDatabaseMetadata().getConnection().createStatement() ) { + final ResultSet rs = s.executeQuery( "SELECT version()" ); + if ( rs.next() ) { + versionString = rs.getString( 1 ); + } + } + catch (SQLException ex) { + // Ignore + } + } + return parseVersion( versionString ); + } + + protected static DatabaseVersion parseVersion(String versionString ) { + DatabaseVersion databaseVersion = null; + // What the DB select returns is similar to "CockroachDB CCL v21.2.10 (x86_64-unknown-linux-gnu, built 2022/05/02 17:38:58, go1.16.6)" + Matcher m = CRDB_VERSION_PATTERN.matcher( versionString == null ? "" : versionString ); + if ( m.find() ) { + String[] versionParts = m.group().substring( 1 ).split( "\\." ); + // if we got to this point, there is at least a major version, so no need to check [].length > 0 + int majorVersion = Integer.parseInt( versionParts[0] ); + int minorVersion = versionParts.length > 1 ? Integer.parseInt( versionParts[1] ) : 0; + int microVersion = versionParts.length > 2 ? Integer.parseInt( versionParts[2] ) : 0; + + databaseVersion= new SimpleDatabaseVersion( majorVersion, minorVersion, microVersion); + } + if ( databaseVersion == null ) { + LOG.unableToDetermineCockroachDatabaseVersion( + MINIMUM_VERSION.getDatabaseMajorVersion() + "." + + MINIMUM_VERSION.getDatabaseMinorVersion() + "." + + MINIMUM_VERSION.getDatabaseMicroVersion() + ); + databaseVersion = MINIMUM_VERSION; + } + return databaseVersion; + } + @Override protected DatabaseVersion getMinimumSupportedVersion() { return MINIMUM_VERSION; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index ab503e85fb..b02afd4769 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1794,4 +1794,8 @@ public interface CoreMessageLogger extends BasicLogger { @Message(value = "The %2$s version for [%s] is no longer supported, hence certain features may not work properly. The minimum supported version is %3$s. Check the community dialects project for available legacy versions.", id = 511) void unsupportedDatabaseVersion(String databaseName, String actualVersion, String minimumVersion); + @LogMessage(level = WARN) + @Message(value = "The database version version for the Cockroach Dialect could not be determined. The minimum supported version (%s) has been set instead.", id = 512) + void unableToDetermineCockroachDatabaseVersion(String minimumVersion); + } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/CockroachDialectVersionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/CockroachDialectVersionTest.java new file mode 100644 index 0000000000..8ff08a01e2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/unit/CockroachDialectVersionTest.java @@ -0,0 +1,125 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.dialect.unit; + +import org.hibernate.dialect.CockroachDialect; +import org.hibernate.dialect.DatabaseVersion; +import org.hibernate.dialect.Dialect; +import org.hibernate.internal.CoreMessageLogger; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.logger.Triggerable; +import org.hibernate.testing.orm.logger.LoggerInspectionExtension; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import org.jboss.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @author Jan Schatteman + */ +public class CockroachDialectVersionTest { + + private Triggerable triggerable; + + @RegisterExtension + public LoggerInspectionExtension logger = LoggerInspectionExtension + .builder().setLogger( + Logger.getMessageLogger( CoreMessageLogger.class, CockroachDialect.class.getName() ) + ).build(); + + @BeforeEach + public void setUp() { + triggerable = logger.watchForLogMessages("HHH000512" ); + triggerable.reset(); + } + + @Test + @TestForIssue(jiraKey = "HHH-15511") + public void testCockroachDialectVersionParsing() { + String failMsg = "HHH000511: The database version version for the Cockroach Dialect could not be determined ... should have been logged"; + + CockroachDBTestDialect testDialect = new CockroachDBTestDialect( null ); + Assertions.assertTrue( triggerable.wasTriggered(), failMsg); + DatabaseVersion dv = testDialect.getVersion(); + assertNotNull( dv ); + assertEquals( testDialect.getMinimumVersion().getDatabaseMajorVersion(), dv.getMajor() ); + assertEquals( testDialect.getMinimumVersion().getDatabaseMinorVersion(), dv.getMinor() ); + assertEquals( testDialect.getMinimumVersion().getDatabaseMicroVersion(), dv.getMicro() ); + triggerable.reset(); + + testDialect = new CockroachDBTestDialect( "" ); + Assertions.assertTrue( triggerable.wasTriggered(), failMsg); + dv = testDialect.getVersion(); + assertNotNull( dv ); + assertEquals( testDialect.getMinimumVersion().getDatabaseMajorVersion(), dv.getMajor() ); + assertEquals( testDialect.getMinimumVersion().getDatabaseMinorVersion(), dv.getMinor() ); + assertEquals( testDialect.getMinimumVersion().getDatabaseMicroVersion(), dv.getMicro() ); + triggerable.reset(); + + testDialect = new CockroachDBTestDialect( "Some version lacking string" ); + Assertions.assertTrue( triggerable.wasTriggered(), failMsg); + dv = testDialect.getVersion(); + assertNotNull( dv ); + assertEquals( testDialect.getMinimumVersion().getDatabaseMajorVersion(), dv.getMajor() ); + assertEquals( testDialect.getMinimumVersion().getDatabaseMinorVersion(), dv.getMinor() ); + assertEquals( testDialect.getMinimumVersion().getDatabaseMicroVersion(), dv.getMicro() ); + triggerable.reset(); + + // using a fictitious major version, to avoid minimum version warnings + Dialect dialect = new CockroachDBTestDialect( "CockroachDB CCL v99.2.10 (x86_64-unknown-linux-gnu, built 2022/05/02 17:38:58, go1.16.6)" ); + + dv = dialect.getVersion(); + assertNotNull( dv ); + assertEquals( 99, dv.getMajor() ); + assertEquals( 2, dv.getMinor() ); + assertEquals( 10, dv.getMicro() ); + + dialect = new CockroachDBTestDialect("CockroachDB CCL v99.2. (x86_64-unknown-linux-gnu, built 2022/05/02 17:38:58, go1.16.6)"); + dv = dialect.getVersion(); + assertNotNull( dv ); + assertEquals( 99, dv.getMajor() ); + assertEquals( 2, dv.getMinor() ); + assertEquals( 0, dv.getMicro() ); + + dialect = new CockroachDBTestDialect("CockroachDB CCL v99.2 (x86_64-unknown-linux-gnu, built 2022/05/02 17:38:58, go1.16.6)"); + dv = dialect.getVersion(); + assertNotNull( dv ); + assertEquals( 99, dv.getMajor() ); + assertEquals( 2, dv.getMinor() ); + assertEquals( 0, dv.getMicro() ); + + dialect = new CockroachDBTestDialect("CockroachDB CCL v99. (x86_64-unknown-linux-gnu, built 2022/05/02 17:38:58, go1.16.6)"); + dv = dialect.getVersion(); + assertNotNull( dv ); + assertEquals( 99, dv.getMajor() ); + assertEquals( 0, dv.getMinor() ); + assertEquals( 0, dv.getMicro() ); + + dialect = new CockroachDBTestDialect("CockroachDB CCL v99 (x86_64-unknown-linux-gnu, built 2022/05/02 17:38:58, go1.16.6)"); + dv = dialect.getVersion(); + assertNotNull( dv ); + assertEquals( 99, dv.getMajor() ); + assertEquals( 0, dv.getMinor() ); + assertEquals( 0, dv.getMicro() ); + } + + private static final class CockroachDBTestDialect extends CockroachDialect { + private CockroachDBTestDialect(String versionString) { + super (parseVersion( versionString )); + } + + private DatabaseVersion getMinimumVersion() { + return getMinimumSupportedVersion(); + } + } +}