Skip to content

Commit 1f5f778

Browse files
committed
HHH-18793 Add JSON aggregate support for MySQL
1 parent a2c714e commit 1f5f778

File tree

10 files changed

+384
-11
lines changed

10 files changed

+384
-11
lines changed

hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import org.hibernate.boot.model.TypeContributions;
1717
import org.hibernate.cfg.Environment;
1818
import org.hibernate.dialect.*;
19+
import org.hibernate.dialect.aggregate.AggregateSupport;
20+
import org.hibernate.dialect.aggregate.MySQLAggregateSupport;
1921
import org.hibernate.dialect.function.CommonFunctionFactory;
2022
import org.hibernate.dialect.identity.IdentityColumnSupport;
2123
import org.hibernate.dialect.identity.MySQLIdentityColumnSupport;
@@ -263,7 +265,10 @@ protected String castType(int sqlTypeCode) {
263265
//MySQL doesn't let you cast to DOUBLE/FLOAT
264266
//but don't just return 'decimal' because
265267
//the default scale is 0 (no decimal places)
266-
return "decimal($p,$s)";
268+
return getMySQLVersion().isSameOrAfter( 8, 0, 17 )
269+
// In newer versions of MySQL, casting to float/double is supported
270+
? super.castType( sqlTypeCode )
271+
: "decimal($p,$s)";
267272
case CHAR:
268273
case NCHAR:
269274
case VARCHAR:
@@ -385,6 +390,13 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
385390
ddlTypeRegistry.addDescriptor( new NativeOrdinalEnumDdlTypeImpl( this ) );
386391
}
387392

393+
@Override
394+
public AggregateSupport getAggregateSupport() {
395+
return getMySQLVersion().isSameOrAfter( 5, 7 )
396+
? MySQLAggregateSupport.JSON_INSTANCE
397+
: super.getAggregateSupport();
398+
}
399+
388400
@Deprecated
389401
protected static int getCharacterSetBytesPerCharacter(DatabaseMetaData databaseMetaData) {
390402
if ( databaseMetaData != null ) {

hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import org.hibernate.type.descriptor.java.CharacterArrayJavaType;
8484
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
8585
import org.hibernate.type.descriptor.jdbc.JdbcType;
86+
import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor;
8687
import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcTypeConstructor;
8788
import org.hibernate.type.descriptor.jdbc.JsonAsStringArrayJdbcTypeConstructor;
8889
import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType;
@@ -101,6 +102,7 @@
101102
import jakarta.persistence.AttributeConverter;
102103

103104
import static org.hibernate.internal.util.collections.CollectionHelper.mutableJoin;
105+
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForArray;
104106
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForDuration;
105107
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForInstant;
106108
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForUuid;
@@ -771,6 +773,14 @@ public void contributeType(CompositeUserType<?> type) {
771773
jdbcTypeRegistry.addTypeConstructor( XmlAsStringArrayJdbcTypeConstructor.INSTANCE );
772774
}
773775
}
776+
if ( jdbcTypeRegistry.getConstructor( SqlTypes.ARRAY ) == null ) {
777+
// Default the array constructor to e.g. JSON_ARRAY/XML_ARRAY if needed
778+
final JdbcTypeConstructor constructor =
779+
jdbcTypeRegistry.getConstructor( getPreferredSqlTypeCodeForArray( serviceRegistry ) );
780+
if ( constructor != null ) {
781+
jdbcTypeRegistry.addTypeConstructor( SqlTypes.ARRAY, constructor );
782+
}
783+
}
774784

775785
final int preferredSqlTypeCodeForDuration = getPreferredSqlTypeCodeForDuration( serviceRegistry );
776786
if ( preferredSqlTypeCodeForDuration != SqlTypes.INTERVAL_SECOND ) {

hibernate-core/src/main/java/org/hibernate/dialect/JsonHelper.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ private static void convertedBasicValueToString(
322322
appender.append( '"' );
323323
break;
324324
case SqlTypes.ARRAY:
325+
case SqlTypes.JSON_ARRAY:
325326
final int length = Array.getLength( value );
326327
appender.append( '[' );
327328
if ( length != 0 ) {

hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import org.hibernate.boot.model.FunctionContributions;
2222
import org.hibernate.boot.model.TypeContributions;
2323
import org.hibernate.cfg.Environment;
24+
import org.hibernate.dialect.aggregate.AggregateSupport;
25+
import org.hibernate.dialect.aggregate.MySQLAggregateSupport;
2426
import org.hibernate.dialect.function.CommonFunctionFactory;
2527
import org.hibernate.dialect.identity.IdentityColumnSupport;
2628
import org.hibernate.dialect.identity.MySQLIdentityColumnSupport;
@@ -316,12 +318,15 @@ protected String castType(int sqlTypeCode) {
316318
// MySQL doesn't let you cast to DOUBLE/FLOAT
317319
// but don't just return 'decimal' because
318320
// the default scale is 0 (no decimal places)
319-
case FLOAT, REAL, DOUBLE -> "decimal($p,$s)";
321+
case FLOAT, REAL, DOUBLE -> getMySQLVersion().isSameOrAfter( 8, 0, 17 )
322+
// In newer versions of MySQL, casting to float/double is supported
323+
? super.castType( sqlTypeCode )
324+
: "decimal($p,$s)";
320325
// MySQL doesn't let you cast to TEXT/LONGTEXT
321-
case CHAR, VARCHAR, LONG32VARCHAR -> "char";
322-
case NCHAR, NVARCHAR, LONG32NVARCHAR -> "char character set utf8mb4";
326+
case CHAR, VARCHAR, LONG32VARCHAR, CLOB -> "char";
327+
case NCHAR, NVARCHAR, LONG32NVARCHAR, NCLOB -> "char character set utf8mb4";
323328
// MySQL doesn't let you cast to BLOB/TINYBLOB/LONGBLOB
324-
case BINARY, VARBINARY, LONG32VARBINARY -> "binary";
329+
case BINARY, VARBINARY, LONG32VARBINARY, BLOB -> "binary";
325330
default -> super.castType(sqlTypeCode);
326331
};
327332
}
@@ -433,6 +438,11 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
433438
ddlTypeRegistry.addDescriptor( new NativeOrdinalEnumDdlTypeImpl( this ) );
434439
}
435440

441+
@Override
442+
public AggregateSupport getAggregateSupport() {
443+
return MySQLAggregateSupport.valueOf( this );
444+
}
445+
436446
@Deprecated(since="6.4")
437447
protected static int getCharacterSetBytesPerCharacter(DatabaseMetaData databaseMetaData) {
438448
if ( databaseMetaData != null ) {

hibernate-core/src/main/java/org/hibernate/dialect/MySQLSqlAstTranslator.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
*/
4949
public class MySQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAstTranslator<T> {
5050

51+
/**
52+
* On MySQL, 1GB or {@code 2^30 - 1} is the maximum size that a char value can be casted.
53+
*/
54+
private static final int MAX_CHAR_SIZE = (1 << 30) - 1;
55+
5156
public MySQLSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
5257
super( sessionFactory, statement );
5358
}
@@ -64,7 +69,7 @@ public static String getSqlType(CastTarget castTarget, SessionFactoryImplementor
6469
private static String getSqlType(CastTarget castTarget, String sqlType, Dialect dialect) {
6570
if ( sqlType != null ) {
6671
int parenthesesIndex = sqlType.indexOf( '(' );
67-
final String baseName = parenthesesIndex == -1 ? sqlType : sqlType.substring( 0, parenthesesIndex );
72+
final String baseName = parenthesesIndex == -1 ? sqlType : sqlType.substring( 0, parenthesesIndex ).trim();
6873
switch ( baseName.toLowerCase( Locale.ROOT ) ) {
6974
case "bit":
7075
return "unsigned";
@@ -76,6 +81,9 @@ private static String getSqlType(CastTarget castTarget, String sqlType, Dialect
7681
case "float":
7782
case "real":
7883
case "double precision":
84+
if ( ((MySQLDialect) dialect).getMySQLVersion().isSameOrAfter( 8, 0, 17 ) ) {
85+
return sqlType;
86+
}
7987
final int precision = castTarget.getPrecision() == null
8088
? dialect.getDefaultDecimalPrecision()
8189
: castTarget.getPrecision();
@@ -85,6 +93,10 @@ private static String getSqlType(CastTarget castTarget, String sqlType, Dialect
8593
case "varchar":
8694
case "nchar":
8795
case "nvarchar":
96+
case "text":
97+
case "mediumtext":
98+
case "longtext":
99+
case "enum":
88100
if ( castTarget.getLength() == null ) {
89101
// TODO: this is ugly and fragile, but could easily be handled in a DdlType
90102
if ( castTarget.getJdbcMapping().getJdbcJavaType().getJavaType() == Character.class ) {
@@ -94,9 +106,11 @@ private static String getSqlType(CastTarget castTarget, String sqlType, Dialect
94106
return "char";
95107
}
96108
}
97-
return "char(" + castTarget.getLength() + ")";
109+
return castTarget.getLength() > MAX_CHAR_SIZE ? "char" : "char(" + castTarget.getLength() + ")";
98110
case "binary":
99111
case "varbinary":
112+
case "mediumblob":
113+
case "longblob":
100114
return castTarget.getLength() == null
101115
? "binary"
102116
: "binary(" + castTarget.getLength() + ")";

hibernate-core/src/main/java/org/hibernate/dialect/aggregate/AggregateSupportImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.hibernate.mapping.Column;
1414
import org.hibernate.metamodel.mapping.SelectableMapping;
1515
import org.hibernate.metamodel.mapping.SqlTypedMapping;
16+
import org.hibernate.type.SqlTypes;
1617
import org.hibernate.type.spi.TypeConfiguration;
1718

1819
public class AggregateSupportImpl implements AggregateSupport {
@@ -76,7 +77,10 @@ public List<AuxiliaryDatabaseObject> aggregateAuxiliaryDatabaseObjects(
7677

7778
@Override
7879
public int aggregateComponentSqlTypeCode(int aggregateColumnSqlTypeCode, int columnSqlTypeCode) {
79-
return columnSqlTypeCode;
80+
return switch (aggregateColumnSqlTypeCode) {
81+
case SqlTypes.JSON -> columnSqlTypeCode == SqlTypes.ARRAY ? SqlTypes.JSON_ARRAY : columnSqlTypeCode;
82+
default -> columnSqlTypeCode;
83+
};
8084
}
8185

8286
@Override

0 commit comments

Comments
 (0)