HHH-15179 - Move support for MySQL versions older than 5.7 into community dialects

Signed-off-by: Jan Schatteman <jschatte@redhat.com>
This commit is contained in:
Jan Schatteman 2022-06-09 23:39:33 +02:00 committed by Christian Beikov
parent 429ab5b936
commit 5b0b1fa680
7 changed files with 1462 additions and 64 deletions

View File

@ -0,0 +1,167 @@
/*
* 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.community.dialect;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
import org.hibernate.sql.ast.tree.Statement;
import org.hibernate.sql.ast.tree.cte.CteStatement;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.Literal;
import org.hibernate.sql.ast.tree.expression.Summarization;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.ValuesTableReference;
import org.hibernate.sql.ast.tree.predicate.BooleanExpressionPredicate;
import org.hibernate.sql.ast.tree.select.QueryGroup;
import org.hibernate.sql.ast.tree.select.QueryPart;
import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.exec.spi.JdbcOperation;
/**
* A SQL AST translator for MySQL.
*
* @author Christian Beikov
*/
public class MySQLLegacySqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
public MySQLLegacySqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
super( sessionFactory, statement );
}
@Override
protected void renderExpressionAsClauseItem(Expression expression) {
expression.accept( this );
}
@Override
public void visitBooleanExpressionPredicate(BooleanExpressionPredicate booleanExpressionPredicate) {
final boolean isNegated = booleanExpressionPredicate.isNegated();
if ( isNegated ) {
appendSql( "not(" );
}
booleanExpressionPredicate.getExpression().accept( this );
if ( isNegated ) {
appendSql( CLOSE_PARENTHESIS );
}
}
@Override
protected String getForShare(int timeoutMillis) {
return getDialect().getVersion().isSameOrAfter( 8 ) ? " for share" : " lock in share mode";
}
protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
// Check if current query part is already row numbering to avoid infinite recursion
return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart
&& getDialect().supportsWindowFunctions() && !isRowsOnlyFetchClauseType( queryPart );
}
@Override
public void visitQueryGroup(QueryGroup queryGroup) {
if ( shouldEmulateFetchClause( queryGroup ) ) {
emulateFetchOffsetWithWindowFunctions( queryGroup, true );
}
else {
super.visitQueryGroup( queryGroup );
}
}
@Override
public void visitQuerySpec(QuerySpec querySpec) {
if ( shouldEmulateFetchClause( querySpec ) ) {
emulateFetchOffsetWithWindowFunctions( querySpec, true );
}
else {
super.visitQuerySpec( querySpec );
}
}
@Override
public void visitValuesTableReference(ValuesTableReference tableReference) {
emulateValuesTableReferenceColumnAliasing( tableReference );
}
@Override
public void visitQueryPartTableReference(QueryPartTableReference tableReference) {
if ( getDialect().getVersion().isSameOrAfter( 8 ) ) {
super.visitQueryPartTableReference( tableReference );
}
else {
emulateQueryPartTableReferenceColumnAliasing( tableReference );
}
}
@Override
public void visitOffsetFetchClause(QueryPart queryPart) {
if ( !isRowNumberingCurrentQueryPart() ) {
renderCombinedLimitClause( queryPart );
}
}
@Override
protected void renderSearchClause(CteStatement cte) {
// MySQL does not support this, but it's just a hint anyway
}
@Override
protected void renderCycleClause(CteStatement cte) {
// MySQL does not support this, but it can be emulated
}
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonDistinctOperator( lhs, operator, rhs );
}
@Override
protected void renderPartitionItem(Expression expression) {
if ( expression instanceof Literal ) {
appendSql( "'0'" );
}
else if ( expression instanceof Summarization ) {
Summarization summarization = (Summarization) expression;
renderCommaSeparated( summarization.getGroupings() );
appendSql( " with " );
appendSql( summarization.getKind().sqlText() );
}
else {
expression.accept( this );
}
}
@Override
public boolean supportsRowValueConstructorSyntaxInSet() {
return false;
}
@Override
public boolean supportsRowValueConstructorSyntaxInInList() {
return getDialect().getVersion().isSameOrAfter( 5, 7 );
}
@Override
protected boolean supportsRowValueConstructorSyntaxInQuantifiedPredicates() {
return false;
}
@Override
protected boolean supportsIntersect() {
return false;
}
@Override
protected boolean supportsDistinctFromPredicate() {
// It supports a proprietary operator
return true;
}
@Override
protected String getFromDual() {
return " from dual";
}
}

View File

@ -80,12 +80,14 @@ import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtract
import static org.hibernate.type.SqlTypes.*; import static org.hibernate.type.SqlTypes.*;
/** /**
* A {@linkplain Dialect SQL dialect} for MySQL 5 and above. * A {@linkplain Dialect SQL dialect} for MySQL 5.7 and above.
* *
* @author Gavin King * @author Gavin King
*/ */
public class MySQLDialect extends Dialect { public class MySQLDialect extends Dialect {
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 5, 7 );
private final UniqueDelegate uniqueDelegate = new MySQLUniqueDelegate( this ); private final UniqueDelegate uniqueDelegate = new MySQLUniqueDelegate( this );
private final MySQLStorageEngine storageEngine = createStorageEngine(); private final MySQLStorageEngine storageEngine = createStorageEngine();
private final SizeStrategy sizeStrategy = new SizeStrategyImpl() { private final SizeStrategy sizeStrategy = new SizeStrategyImpl() {
@ -111,7 +113,7 @@ public class MySQLDialect extends Dialect {
private final int maxVarbinaryLength; private final int maxVarbinaryLength;
public MySQLDialect() { public MySQLDialect() {
this( DatabaseVersion.make( 5, 0 ) ); this( MINIMUM_VERSION );
} }
public MySQLDialect(DatabaseVersion version) { public MySQLDialect(DatabaseVersion version) {
@ -128,6 +130,11 @@ public class MySQLDialect extends Dialect {
maxVarbinaryLength = maxVarbinaryLength( getMySQLVersion() ); maxVarbinaryLength = maxVarbinaryLength( getMySQLVersion() );
} }
@Override
protected DatabaseVersion getMinimumSupportedVersion() {
return MINIMUM_VERSION;
}
@Override @Override
protected void initDefaultProperties() { protected void initDefaultProperties() {
super.initDefaultProperties(); super.initDefaultProperties();
@ -161,11 +168,9 @@ public class MySQLDialect extends Dialect {
return "bit"; return "bit";
case TIMESTAMP: case TIMESTAMP:
return getMySQLVersion().isBefore( 5, 7 ) return "datetime($p)";
? "datetime" : "datetime($p)";
case TIMESTAMP_WITH_TIMEZONE: case TIMESTAMP_WITH_TIMEZONE:
return getMySQLVersion().isBefore( 5, 7 ) return "timestamp($p)";
? "timestamp" : "timestamp($p)";
case NUMERIC: case NUMERIC:
// it's just a synonym // it's just a synonym
return columnType( DECIMAL ); return columnType( DECIMAL );
@ -221,11 +226,9 @@ public class MySQLDialect extends Dialect {
super.registerColumnTypes( typeContributions, serviceRegistry ); super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry(); final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
// MySQL 5.7 brings JSON native support with a dedicated datatype // MySQL 5.7 brings JSON native support with a dedicated datatype
// https://dev.mysql.com/doc/refman/5.7/en/json.html // https://dev.mysql.com/doc/refman/5.7/en/json.html
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
}
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) );
@ -346,15 +349,10 @@ public class MySQLDialect extends Dialect {
} }
private static int maxVarbinaryLength(DatabaseVersion version) { private static int maxVarbinaryLength(DatabaseVersion version) {
return version.isBefore( 5 ) ? 255 : 65_535; return 65_535;
} }
private static int maxVarcharLength(DatabaseVersion version, int bytesPerCharacter) { private static int maxVarcharLength(DatabaseVersion version, int bytesPerCharacter) {
// max length for VARCHAR changed in 5.0.3
if ( version.isBefore( 5 ) ) {
return 255;
}
else {
switch ( bytesPerCharacter ) { switch ( bytesPerCharacter ) {
case 1: case 1:
return 65_535; return 65_535;
@ -367,7 +365,6 @@ public class MySQLDialect extends Dialect {
return 16_383; return 16_383;
} }
} }
}
@Override @Override
public int getMaxVarcharLength() { public int getMaxVarcharLength() {
@ -501,10 +498,6 @@ public class MySQLDialect extends Dialect {
.setUseParenthesesWhenNoArgs( false ) .setUseParenthesesWhenNoArgs( false )
.register(); .register();
if ( getMySQLVersion().isBefore( 5, 7 ) ) {
functionFactory.sysdateParens();
}
else {
// MySQL timestamp type defaults to precision 0 (seconds) but // MySQL timestamp type defaults to precision 0 (seconds) but
// we want the standard default precision of 6 (microseconds) // we want the standard default precision of 6 (microseconds)
functionFactory.sysdateExplicitMicros(); functionFactory.sysdateExplicitMicros();
@ -514,7 +507,6 @@ public class MySQLDialect extends Dialect {
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation(); functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
} }
} }
}
functionFactory.listagg_groupConcat(); functionFactory.listagg_groupConcat();
} }
@ -526,9 +518,7 @@ public class MySQLDialect extends Dialect {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.getJdbcTypeRegistry(); .getJdbcTypeRegistry();
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE );
}
// MySQL requires a custom binder for binding untyped nulls with the NULL type // MySQL requires a custom binder for binding untyped nulls with the NULL type
typeContributions.contributeJdbcType( NullJdbcType.INSTANCE ); typeContributions.contributeJdbcType( NullJdbcType.INSTANCE );
@ -595,7 +585,7 @@ public class MySQLDialect extends Dialect {
*/ */
@Override @Override
public String currentTimestamp() { public String currentTimestamp() {
return getMySQLVersion().isBefore( 5, 7 ) ? super.currentTimestamp() : "current_timestamp(6)"; return "current_timestamp(6)";
} }
// for consistency, we could do this: but I decided not to // for consistency, we could do this: but I decided not to
@ -675,7 +665,7 @@ public class MySQLDialect extends Dialect {
@Override @Override
public boolean supportsUnionAll() { public boolean supportsUnionAll() {
return getMySQLVersion().isSameOrAfter( 5 ); return true;
} }
@Override @Override
@ -690,9 +680,7 @@ public class MySQLDialect extends Dialect {
@Override @Override
public String getQueryHintString(String query, String hints) { public String getQueryHintString(String query, String hints) {
return getMySQLVersion().isBefore( 5 ) return IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints );
? super.getQueryHintString( query, hints )
: IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints );
} }
/** /**
@ -704,7 +692,7 @@ public class MySQLDialect extends Dialect {
} }
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() { public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return getMySQLVersion().isBefore( 5 ) ? super.getViolatedConstraintNameExtractor() : EXTRACTOR; return EXTRACTOR;
} }
private static final ViolatedConstraintNameExtractor EXTRACTOR = private static final ViolatedConstraintNameExtractor EXTRACTOR =
@ -1005,8 +993,7 @@ public class MySQLDialect extends Dialect {
@Override @Override
public String getTableTypeString() { public String getTableTypeString() {
String engineKeyword = getMySQLVersion().isBefore( 5 ) ? "type" : "engine"; return storageEngine.getTableTypeString( "engine" );
return storageEngine.getTableTypeString( engineKeyword );
} }
@Override @Override
@ -1020,7 +1007,7 @@ public class MySQLDialect extends Dialect {
} }
protected MySQLStorageEngine getDefaultMySQLStorageEngine() { protected MySQLStorageEngine getDefaultMySQLStorageEngine() {
return getMySQLVersion().isBefore( 5, 5 ) ? MyISAMStorageEngine.INSTANCE : InnoDBStorageEngine.INSTANCE; return InnoDBStorageEngine.INSTANCE;
} }
@Override @Override

View File

@ -141,7 +141,7 @@ public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlA
@Override @Override
public boolean supportsRowValueConstructorSyntaxInInList() { public boolean supportsRowValueConstructorSyntaxInInList() {
return getDialect().getVersion().isSameOrAfter( 5, 7 ); return true;
} }
@Override @Override

View File

@ -8,16 +8,13 @@ package org.hibernate.orm.test.dialect.unit.lockhint;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
import org.hibernate.dialect.MySQL57Dialect; import org.hibernate.dialect.MySQLDialect;
import org.hibernate.testing.junit4.BaseUnitTestCase; import org.hibernate.testing.junit4.BaseUnitTestCase;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Properties; import java.util.Properties;
@ -26,7 +23,7 @@ public class MySQLStorageEngineTest extends BaseUnitTestCase {
@Test @Test
public void testDefaultStorage() { public void testDefaultStorage() {
assertEquals( " engine=InnoDB", new MySQL57Dialect().getTableTypeString() ); assertEquals( " engine=InnoDB", new MySQLDialect().getTableTypeString() );
} }
@Test @Test
@ -37,7 +34,7 @@ public class MySQLStorageEngineTest extends BaseUnitTestCase {
assertNotNull( systemProperties ); assertNotNull( systemProperties );
final Object previousValue = systemProperties.setProperty( AvailableSettings.STORAGE_ENGINE, "myisam" ); final Object previousValue = systemProperties.setProperty( AvailableSettings.STORAGE_ENGINE, "myisam" );
try { try {
assertEquals( " engine=MyISAM", new MySQL57Dialect().getTableTypeString() ); assertEquals( " engine=MyISAM", new MySQLDialect().getTableTypeString() );
} }
finally { finally {
if ( previousValue != null ) { if ( previousValue != null ) {

View File

@ -27,11 +27,11 @@ import static org.junit.Assert.assertTrue;
* @author Gail Badner. * @author Gail Badner.
*/ */
@TestForIssue( jiraKey = "HHH-8401") @TestForIssue( jiraKey = "HHH-8401")
@RequiresDialect( value = MySQLDialect.class, majorVersion = 5, minorVersion = 7) @RequiresDialect( value = MySQLDialect.class)
@ServiceRegistry @ServiceRegistry
@DomainModel @DomainModel
@SessionFactory @SessionFactory
public class MySQL57TimestampFspFunctionTest { public class MySQLTimestampFspFunctionTest {
@Test @Test
public void testTimeStampFunctions(SessionFactoryScope scope) { public void testTimeStampFunctions(SessionFactoryScope scope) {

View File

@ -36,13 +36,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
/** /**
* @author Gail Badner * @author Gail Badner
*/ */
@RequiresDialect(value = MySQLDialect.class, majorVersion = 5, minorVersion = 7) @RequiresDialect(value = MySQLDialect.class)
@TestForIssue(jiraKey = "HHH-8401") @TestForIssue(jiraKey = "HHH-8401")
@DomainModel( @DomainModel(
annotatedClasses = MySQL57TimestampPropertyTest.Entity.class annotatedClasses = MySQLTimestampPropertyTest.Entity.class
) )
@SessionFactory @SessionFactory
public class MySQL57TimestampPropertyTest { public class MySQLTimestampPropertyTest {
private final DateFormat timestampFormat = new SimpleDateFormat( "HH:mm:ss.SSS" ); private final DateFormat timestampFormat = new SimpleDateFormat( "HH:mm:ss.SSS" );
@Test @Test