Skip to content

Commit 243306d

Browse files
committed
HHH-18794 Add JSON aggregate support for MariaDB
1 parent 1f5f778 commit 243306d

29 files changed

+379
-42
lines changed

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

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
import org.hibernate.boot.model.FunctionContributions;
1111
import org.hibernate.boot.model.TypeContributions;
1212
import org.hibernate.dialect.*;
13+
import org.hibernate.dialect.aggregate.AggregateSupport;
14+
import org.hibernate.dialect.aggregate.AggregateSupportImpl;
15+
import org.hibernate.dialect.aggregate.MySQLAggregateSupport;
1316
import org.hibernate.dialect.function.CommonFunctionFactory;
1417
import org.hibernate.dialect.sequence.MariaDBSequenceSupport;
1518
import org.hibernate.dialect.sequence.SequenceSupport;
@@ -18,6 +21,7 @@
1821
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
1922
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
2023
import org.hibernate.engine.spi.SessionFactoryImplementor;
24+
import org.hibernate.query.sqm.CastType;
2125
import org.hibernate.service.ServiceRegistry;
2226
import org.hibernate.sql.ast.SqlAstTranslator;
2327
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
@@ -29,8 +33,6 @@
2933
import org.hibernate.type.SqlTypes;
3034
import org.hibernate.type.StandardBasicTypes;
3135
import org.hibernate.type.descriptor.jdbc.JdbcType;
32-
import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcTypeConstructor;
33-
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
3436
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
3537
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
3638
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
@@ -122,6 +124,13 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
122124
}
123125
}
124126

127+
@Override
128+
public AggregateSupport getAggregateSupport() {
129+
return getVersion().isSameOrAfter( 10, 2 )
130+
? MySQLAggregateSupport.LONGTEXT_INSTANCE
131+
: AggregateSupportImpl.INSTANCE;
132+
}
133+
125134
@Override
126135
public JdbcType resolveSqlTypeDescriptor(
127136
String columnTypeName,
@@ -150,15 +159,22 @@ public JdbcType resolveSqlTypeDescriptor(
150159
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
151160
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
152161
// Make sure we register the JSON type descriptor before calling super, because MariaDB does not need casting
153-
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE );
154-
jdbcTypeRegistry.addTypeConstructorIfAbsent( JsonArrayJdbcTypeConstructor.INSTANCE );
162+
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, MariaDBCastingJsonJdbcType.INSTANCE );
163+
jdbcTypeRegistry.addTypeConstructorIfAbsent( MariaDBCastingJsonArrayJdbcTypeConstructor.INSTANCE );
155164

156165
super.contributeTypes( typeContributions, serviceRegistry );
157166
if ( getVersion().isSameOrAfter( 10, 7 ) ) {
158167
jdbcTypeRegistry.addDescriptorIfAbsent( VarcharUUIDJdbcType.INSTANCE );
159168
}
160169
}
161170

171+
@Override
172+
public String castPattern(CastType from, CastType to) {
173+
return to == CastType.JSON
174+
? "json_extract(?1,'$')"
175+
: super.castPattern( from, to );
176+
}
177+
162178
@Override
163179
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
164180
return new StandardSqlAstTranslatorFactory() {

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

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.hibernate.dialect.MySQLSqlAstTranslator;
1212
import org.hibernate.engine.spi.SessionFactoryImplementor;
1313
import org.hibernate.internal.util.collections.Stack;
14+
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
1415
import org.hibernate.query.sqm.ComparisonOperator;
1516
import org.hibernate.sql.ast.Clause;
1617
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
@@ -292,7 +293,54 @@ public void visitOffsetFetchClause(QueryPart queryPart) {
292293

293294
@Override
294295
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
295-
renderComparisonDistinctOperator( lhs, operator, rhs );
296+
final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType();
297+
if ( lhsExpressionType != null && lhsExpressionType.getJdbcTypeCount() == 1
298+
&& lhsExpressionType.getSingleJdbcMapping().getJdbcType().isJson() ) {
299+
switch ( operator ) {
300+
case DISTINCT_FROM:
301+
appendSql( "case when json_equals(" );
302+
lhs.accept( this );
303+
appendSql( ',' );
304+
rhs.accept( this );
305+
appendSql( ")=1 or " );
306+
lhs.accept( this );
307+
appendSql( " is null and " );
308+
rhs.accept( this );
309+
appendSql( " is null then 0 else 1 end=1" );
310+
break;
311+
case NOT_DISTINCT_FROM:
312+
appendSql( "case when json_equals(" );
313+
lhs.accept( this );
314+
appendSql( ',' );
315+
rhs.accept( this );
316+
appendSql( ")=1 or " );
317+
lhs.accept( this );
318+
appendSql( " is null and " );
319+
rhs.accept( this );
320+
appendSql( " is null then 0 else 1 end=0" );
321+
break;
322+
case NOT_EQUAL:
323+
appendSql( "json_equals(" );
324+
lhs.accept( this );
325+
appendSql( ',' );
326+
rhs.accept( this );
327+
appendSql( ")=0" );
328+
break;
329+
case EQUAL:
330+
appendSql( "json_equals(" );
331+
lhs.accept( this );
332+
appendSql( ',' );
333+
rhs.accept( this );
334+
appendSql( ")=1" );
335+
break;
336+
default:
337+
renderComparisonDistinctOperator( lhs, operator, rhs );
338+
break;
339+
}
340+
}
341+
else {
342+
renderComparisonDistinctOperator( lhs, operator, rhs );
343+
}
296344
}
297345

298346
@Override
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.dialect;
6+
7+
import org.hibernate.sql.ast.spi.SqlAppender;
8+
import org.hibernate.type.descriptor.jdbc.JdbcType;
9+
import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType;
10+
11+
/**
12+
* @author Christian Beikov
13+
*/
14+
public class MariaDBCastingJsonArrayJdbcType extends JsonArrayJdbcType {
15+
16+
public MariaDBCastingJsonArrayJdbcType(JdbcType elementJdbcType) {
17+
super( elementJdbcType );
18+
}
19+
20+
@Override
21+
public void appendWriteExpression(
22+
String writeExpression,
23+
SqlAppender appender,
24+
Dialect dialect) {
25+
appender.append( "json_extract(" );
26+
appender.append( writeExpression );
27+
appender.append( ",'$')" );
28+
}
29+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.dialect;
6+
7+
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
8+
import org.hibernate.type.BasicType;
9+
import org.hibernate.type.SqlTypes;
10+
import org.hibernate.type.descriptor.jdbc.JdbcType;
11+
import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor;
12+
import org.hibernate.type.spi.TypeConfiguration;
13+
14+
/**
15+
* Factory for {@link MariaDBCastingJsonArrayJdbcType}.
16+
*/
17+
public class MariaDBCastingJsonArrayJdbcTypeConstructor implements JdbcTypeConstructor {
18+
19+
public static final MariaDBCastingJsonArrayJdbcTypeConstructor INSTANCE = new MariaDBCastingJsonArrayJdbcTypeConstructor();
20+
21+
@Override
22+
public JdbcType resolveType(
23+
TypeConfiguration typeConfiguration,
24+
Dialect dialect,
25+
BasicType<?> elementType,
26+
ColumnTypeInformation columnTypeInformation) {
27+
return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation );
28+
}
29+
30+
@Override
31+
public JdbcType resolveType(
32+
TypeConfiguration typeConfiguration,
33+
Dialect dialect,
34+
JdbcType elementType,
35+
ColumnTypeInformation columnTypeInformation) {
36+
return new MariaDBCastingJsonArrayJdbcType( elementType );
37+
}
38+
39+
@Override
40+
public int getDefaultSqlTypeCode() {
41+
return SqlTypes.JSON_ARRAY;
42+
}
43+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.dialect;
6+
7+
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
8+
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
9+
import org.hibernate.sql.ast.spi.SqlAppender;
10+
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
11+
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
12+
13+
/**
14+
* @author Christian Beikov
15+
*/
16+
public class MariaDBCastingJsonJdbcType extends JsonJdbcType {
17+
/**
18+
* Singleton access
19+
*/
20+
public static final JsonJdbcType INSTANCE = new MariaDBCastingJsonJdbcType( null );
21+
22+
public MariaDBCastingJsonJdbcType(EmbeddableMappingType embeddableMappingType) {
23+
super( embeddableMappingType );
24+
}
25+
26+
@Override
27+
public AggregateJdbcType resolveAggregateJdbcType(
28+
EmbeddableMappingType mappingType,
29+
String sqlType,
30+
RuntimeModelCreationContext creationContext) {
31+
return new MariaDBCastingJsonJdbcType( mappingType );
32+
}
33+
34+
@Override
35+
public void appendWriteExpression(
36+
String writeExpression,
37+
SqlAppender appender,
38+
Dialect dialect) {
39+
appender.append( "json_extract(" );
40+
appender.append( writeExpression );
41+
appender.append( ",'$')" );
42+
}
43+
}

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import org.hibernate.boot.model.FunctionContributions;
1111
import org.hibernate.boot.model.TypeContributions;
12+
import org.hibernate.dialect.aggregate.AggregateSupport;
13+
import org.hibernate.dialect.aggregate.MySQLAggregateSupport;
1214
import org.hibernate.dialect.function.CommonFunctionFactory;
1315
import org.hibernate.dialect.identity.IdentityColumnSupport;
1416
import org.hibernate.dialect.identity.MariaDBIdentityColumnSupport;
@@ -19,6 +21,7 @@
1921
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
2022
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
2123
import org.hibernate.engine.spi.SessionFactoryImplementor;
24+
import org.hibernate.query.sqm.CastType;
2225
import org.hibernate.service.ServiceRegistry;
2326
import org.hibernate.sql.ast.SqlAstTranslator;
2427
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
@@ -30,8 +33,6 @@
3033
import org.hibernate.type.SqlTypes;
3134
import org.hibernate.type.StandardBasicTypes;
3235
import org.hibernate.type.descriptor.jdbc.JdbcType;
33-
import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcTypeConstructor;
34-
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
3536
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
3637
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
3738
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
@@ -122,6 +123,11 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
122123
}
123124
}
124125

126+
@Override
127+
public AggregateSupport getAggregateSupport() {
128+
return MySQLAggregateSupport.LONGTEXT_INSTANCE;
129+
}
130+
125131
@Override
126132
protected void registerKeyword(String word) {
127133
// The MariaDB driver reports that "STRING" is a keyword, but
@@ -156,16 +162,23 @@ public JdbcType resolveSqlTypeDescriptor(
156162
@Override
157163
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
158164
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
159-
// Make sure we register the JSON type descriptor before calling super, because MariaDB does not need casting
160-
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE );
161-
jdbcTypeRegistry.addTypeConstructorIfAbsent( JsonArrayJdbcTypeConstructor.INSTANCE );
165+
// Make sure we register the JSON type descriptor before calling super, because MariaDB needs special casting
166+
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, MariaDBCastingJsonJdbcType.INSTANCE );
167+
jdbcTypeRegistry.addTypeConstructorIfAbsent( MariaDBCastingJsonArrayJdbcTypeConstructor.INSTANCE );
162168

163169
super.contributeTypes( typeContributions, serviceRegistry );
164170
if ( getVersion().isSameOrAfter( 10, 7 ) ) {
165171
jdbcTypeRegistry.addDescriptorIfAbsent( VarcharUUIDJdbcType.INSTANCE );
166172
}
167173
}
168174

175+
@Override
176+
public String castPattern(CastType from, CastType to) {
177+
return to == CastType.JSON
178+
? "json_extract(?1,'$')"
179+
: super.castPattern( from, to );
180+
}
181+
169182
@Override
170183
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
171184
return new StandardSqlAstTranslatorFactory() {

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

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import org.hibernate.engine.spi.SessionFactoryImplementor;
1111
import org.hibernate.internal.util.collections.Stack;
12+
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
1213
import org.hibernate.query.sqm.ComparisonOperator;
1314
import org.hibernate.sql.ast.Clause;
1415
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
@@ -295,7 +296,54 @@ public void visitOffsetFetchClause(QueryPart queryPart) {
295296

296297
@Override
297298
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
298-
renderComparisonDistinctOperator( lhs, operator, rhs );
299+
final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType();
300+
if ( lhsExpressionType != null && lhsExpressionType.getJdbcTypeCount() == 1
301+
&& lhsExpressionType.getSingleJdbcMapping().getJdbcType().isJson() ) {
302+
switch ( operator ) {
303+
case DISTINCT_FROM:
304+
appendSql( "case when json_equals(" );
305+
lhs.accept( this );
306+
appendSql( ',' );
307+
rhs.accept( this );
308+
appendSql( ")=1 or " );
309+
lhs.accept( this );
310+
appendSql( " is null and " );
311+
rhs.accept( this );
312+
appendSql( " is null then 0 else 1 end=1" );
313+
break;
314+
case NOT_DISTINCT_FROM:
315+
appendSql( "case when json_equals(" );
316+
lhs.accept( this );
317+
appendSql( ',' );
318+
rhs.accept( this );
319+
appendSql( ")=1 or " );
320+
lhs.accept( this );
321+
appendSql( " is null and " );
322+
rhs.accept( this );
323+
appendSql( " is null then 0 else 1 end=0" );
324+
break;
325+
case NOT_EQUAL:
326+
appendSql( "json_equals(" );
327+
lhs.accept( this );
328+
appendSql( ',' );
329+
rhs.accept( this );
330+
appendSql( ")=0" );
331+
break;
332+
case EQUAL:
333+
appendSql( "json_equals(" );
334+
lhs.accept( this );
335+
appendSql( ',' );
336+
rhs.accept( this );
337+
appendSql( ")=1" );
338+
break;
339+
default:
340+
renderComparisonDistinctOperator( lhs, operator, rhs );
341+
break;
342+
}
343+
}
344+
else {
345+
renderComparisonDistinctOperator( lhs, operator, rhs );
346+
}
299347
}
300348

301349
@Override

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR
440440

441441
@Override
442442
public AggregateSupport getAggregateSupport() {
443-
return MySQLAggregateSupport.valueOf( this );
443+
return MySQLAggregateSupport.JSON_INSTANCE;
444444
}
445445

446446
@Deprecated(since="6.4")

0 commit comments

Comments
 (0)