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.*;
/**
* A {@linkplain Dialect SQL dialect} for MySQL 5 and above.
* A {@linkplain Dialect SQL dialect} for MySQL 5.7 and above.
*
* @author Gavin King
*/
public class MySQLDialect extends Dialect {
private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 5, 7 );
private final UniqueDelegate uniqueDelegate = new MySQLUniqueDelegate( this );
private final MySQLStorageEngine storageEngine = createStorageEngine();
private final SizeStrategy sizeStrategy = new SizeStrategyImpl() {
@ -111,7 +113,7 @@ public class MySQLDialect extends Dialect {
private final int maxVarbinaryLength;
public MySQLDialect() {
this( DatabaseVersion.make( 5, 0 ) );
this( MINIMUM_VERSION );
}
public MySQLDialect(DatabaseVersion version) {
@ -128,6 +130,11 @@ public class MySQLDialect extends Dialect {
maxVarbinaryLength = maxVarbinaryLength( getMySQLVersion() );
}
@Override
protected DatabaseVersion getMinimumSupportedVersion() {
return MINIMUM_VERSION;
}
@Override
protected void initDefaultProperties() {
super.initDefaultProperties();
@ -161,11 +168,9 @@ public class MySQLDialect extends Dialect {
return "bit";
case TIMESTAMP:
return getMySQLVersion().isBefore( 5, 7 )
? "datetime" : "datetime($p)";
return "datetime($p)";
case TIMESTAMP_WITH_TIMEZONE:
return getMySQLVersion().isBefore( 5, 7 )
? "timestamp" : "timestamp($p)";
return "timestamp($p)";
case NUMERIC:
// it's just a synonym
return columnType( DECIMAL );
@ -221,11 +226,9 @@ public class MySQLDialect extends Dialect {
super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry();
if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) {
// MySQL 5.7 brings JSON native support with a dedicated datatype
// https://dev.mysql.com/doc/refman/5.7/en/json.html
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
}
// MySQL 5.7 brings JSON native support with a dedicated datatype
// https://dev.mysql.com/doc/refman/5.7/en/json.html
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) );
@ -346,26 +349,20 @@ public class MySQLDialect extends Dialect {
}
private static int maxVarbinaryLength(DatabaseVersion version) {
return version.isBefore( 5 ) ? 255 : 65_535;
return 65_535;
}
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 ) {
case 1:
return 65_535;
case 2:
return 32_767;
case 3:
return 21_844;
case 4:
default:
return 16_383;
}
switch ( bytesPerCharacter ) {
case 1:
return 65_535;
case 2:
return 32_767;
case 3:
return 21_844;
case 4:
default:
return 16_383;
}
}
@ -501,18 +498,13 @@ public class MySQLDialect extends Dialect {
.setUseParenthesesWhenNoArgs( false )
.register();
if ( getMySQLVersion().isBefore( 5, 7 ) ) {
functionFactory.sysdateParens();
}
else {
// MySQL timestamp type defaults to precision 0 (seconds) but
// we want the standard default precision of 6 (microseconds)
functionFactory.sysdateExplicitMicros();
if ( getMySQLVersion().isSameOrAfter( 8, 2 ) ) {
functionFactory.windowFunctions();
if ( getMySQLVersion().isSameOrAfter( 8, 11 ) ) {
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
}
// MySQL timestamp type defaults to precision 0 (seconds) but
// we want the standard default precision of 6 (microseconds)
functionFactory.sysdateExplicitMicros();
if ( getMySQLVersion().isSameOrAfter( 8, 2 ) ) {
functionFactory.windowFunctions();
if ( getMySQLVersion().isSameOrAfter( 8, 11 ) ) {
functionFactory.hypotheticalOrderedSetAggregates_windowEmulation();
}
}
@ -526,9 +518,7 @@ public class MySQLDialect extends Dialect {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration()
.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
typeContributions.contributeJdbcType( NullJdbcType.INSTANCE );
@ -595,7 +585,7 @@ public class MySQLDialect extends Dialect {
*/
@Override
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
@ -675,7 +665,7 @@ public class MySQLDialect extends Dialect {
@Override
public boolean supportsUnionAll() {
return getMySQLVersion().isSameOrAfter( 5 );
return true;
}
@Override
@ -690,9 +680,7 @@ public class MySQLDialect extends Dialect {
@Override
public String getQueryHintString(String query, String hints) {
return getMySQLVersion().isBefore( 5 )
? super.getQueryHintString( query, hints )
: IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints );
return IndexQueryHintHandler.INSTANCE.addQueryHints( query, hints );
}
/**
@ -704,7 +692,7 @@ public class MySQLDialect extends Dialect {
}
public ViolatedConstraintNameExtractor getViolatedConstraintNameExtractor() {
return getMySQLVersion().isBefore( 5 ) ? super.getViolatedConstraintNameExtractor() : EXTRACTOR;
return EXTRACTOR;
}
private static final ViolatedConstraintNameExtractor EXTRACTOR =
@ -1005,8 +993,7 @@ public class MySQLDialect extends Dialect {
@Override
public String getTableTypeString() {
String engineKeyword = getMySQLVersion().isBefore( 5 ) ? "type" : "engine";
return storageEngine.getTableTypeString( engineKeyword );
return storageEngine.getTableTypeString( "engine" );
}
@Override
@ -1020,7 +1007,7 @@ public class MySQLDialect extends Dialect {
}
protected MySQLStorageEngine getDefaultMySQLStorageEngine() {
return getMySQLVersion().isBefore( 5, 5 ) ? MyISAMStorageEngine.INSTANCE : InnoDBStorageEngine.INSTANCE;
return InnoDBStorageEngine.INSTANCE;
}
@Override

View File

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

View File

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

View File

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

View File

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