HHH-18794 Add JSON aggregate support for MariaDB

This commit is contained in:
Christian Beikov 2024-11-05 20:58:36 +01:00
parent 1f5f778358
commit 243306d12e
29 changed files with 379 additions and 42 deletions

View File

@ -10,6 +10,9 @@ import java.sql.SQLException;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.*;
import org.hibernate.dialect.aggregate.AggregateSupport;
import org.hibernate.dialect.aggregate.AggregateSupportImpl;
import org.hibernate.dialect.aggregate.MySQLAggregateSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.sequence.MariaDBSequenceSupport;
import org.hibernate.dialect.sequence.SequenceSupport;
@ -18,6 +21,7 @@ import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.CastType;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
@ -29,8 +33,6 @@ import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcTypeConstructor;
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
@ -122,6 +124,13 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
}
}
@Override
public AggregateSupport getAggregateSupport() {
return getVersion().isSameOrAfter( 10, 2 )
? MySQLAggregateSupport.LONGTEXT_INSTANCE
: AggregateSupportImpl.INSTANCE;
}
@Override
public JdbcType resolveSqlTypeDescriptor(
String columnTypeName,
@ -150,8 +159,8 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
// Make sure we register the JSON type descriptor before calling super, because MariaDB does not need casting
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE );
jdbcTypeRegistry.addTypeConstructorIfAbsent( JsonArrayJdbcTypeConstructor.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, MariaDBCastingJsonJdbcType.INSTANCE );
jdbcTypeRegistry.addTypeConstructorIfAbsent( MariaDBCastingJsonArrayJdbcTypeConstructor.INSTANCE );
super.contributeTypes( typeContributions, serviceRegistry );
if ( getVersion().isSameOrAfter( 10, 7 ) ) {
@ -159,6 +168,13 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect {
}
}
@Override
public String castPattern(CastType from, CastType to) {
return to == CastType.JSON
? "json_extract(?1,'$')"
: super.castPattern( from, to );
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() {

View File

@ -11,6 +11,7 @@ import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
import org.hibernate.dialect.MySQLSqlAstTranslator;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
@ -292,7 +293,54 @@ public class MariaDBLegacySqlAstTranslator<T extends JdbcOperation> extends Abst
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonDistinctOperator( lhs, operator, rhs );
final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType();
if ( lhsExpressionType != null && lhsExpressionType.getJdbcTypeCount() == 1
&& lhsExpressionType.getSingleJdbcMapping().getJdbcType().isJson() ) {
switch ( operator ) {
case DISTINCT_FROM:
appendSql( "case when json_equals(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( ")=1 or " );
lhs.accept( this );
appendSql( " is null and " );
rhs.accept( this );
appendSql( " is null then 0 else 1 end=1" );
break;
case NOT_DISTINCT_FROM:
appendSql( "case when json_equals(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( ")=1 or " );
lhs.accept( this );
appendSql( " is null and " );
rhs.accept( this );
appendSql( " is null then 0 else 1 end=0" );
break;
case NOT_EQUAL:
appendSql( "json_equals(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( ")=0" );
break;
case EQUAL:
appendSql( "json_equals(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( ")=1" );
break;
default:
renderComparisonDistinctOperator( lhs, operator, rhs );
break;
}
}
else {
renderComparisonDistinctOperator( lhs, operator, rhs );
}
}
@Override

View File

@ -0,0 +1,29 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcType;
/**
* @author Christian Beikov
*/
public class MariaDBCastingJsonArrayJdbcType extends JsonArrayJdbcType {
public MariaDBCastingJsonArrayJdbcType(JdbcType elementJdbcType) {
super( elementJdbcType );
}
@Override
public void appendWriteExpression(
String writeExpression,
SqlAppender appender,
Dialect dialect) {
appender.append( "json_extract(" );
appender.append( writeExpression );
appender.append( ",'$')" );
}
}

View File

@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect;
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
import org.hibernate.type.BasicType;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeConstructor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* Factory for {@link MariaDBCastingJsonArrayJdbcType}.
*/
public class MariaDBCastingJsonArrayJdbcTypeConstructor implements JdbcTypeConstructor {
public static final MariaDBCastingJsonArrayJdbcTypeConstructor INSTANCE = new MariaDBCastingJsonArrayJdbcTypeConstructor();
@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
BasicType<?> elementType,
ColumnTypeInformation columnTypeInformation) {
return resolveType( typeConfiguration, dialect, elementType.getJdbcType(), columnTypeInformation );
}
@Override
public JdbcType resolveType(
TypeConfiguration typeConfiguration,
Dialect dialect,
JdbcType elementType,
ColumnTypeInformation columnTypeInformation) {
return new MariaDBCastingJsonArrayJdbcType( elementType );
}
@Override
public int getDefaultSqlTypeCode() {
return SqlTypes.JSON_ARRAY;
}
}

View File

@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.dialect;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
import org.hibernate.sql.ast.spi.SqlAppender;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
/**
* @author Christian Beikov
*/
public class MariaDBCastingJsonJdbcType extends JsonJdbcType {
/**
* Singleton access
*/
public static final JsonJdbcType INSTANCE = new MariaDBCastingJsonJdbcType( null );
public MariaDBCastingJsonJdbcType(EmbeddableMappingType embeddableMappingType) {
super( embeddableMappingType );
}
@Override
public AggregateJdbcType resolveAggregateJdbcType(
EmbeddableMappingType mappingType,
String sqlType,
RuntimeModelCreationContext creationContext) {
return new MariaDBCastingJsonJdbcType( mappingType );
}
@Override
public void appendWriteExpression(
String writeExpression,
SqlAppender appender,
Dialect dialect) {
appender.append( "json_extract(" );
appender.append( writeExpression );
appender.append( ",'$')" );
}
}

View File

@ -9,6 +9,8 @@ import java.sql.SQLException;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.dialect.aggregate.AggregateSupport;
import org.hibernate.dialect.aggregate.MySQLAggregateSupport;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.identity.IdentityColumnSupport;
import org.hibernate.dialect.identity.MariaDBIdentityColumnSupport;
@ -19,6 +21,7 @@ import org.hibernate.engine.jdbc.env.spi.IdentifierCaseStrategy;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelper;
import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.sqm.CastType;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.SqlAstTranslator;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
@ -30,8 +33,6 @@ import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JsonArrayJdbcTypeConstructor;
import org.hibernate.type.descriptor.jdbc.JsonJdbcType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl;
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
@ -122,6 +123,11 @@ public class MariaDBDialect extends MySQLDialect {
}
}
@Override
public AggregateSupport getAggregateSupport() {
return MySQLAggregateSupport.LONGTEXT_INSTANCE;
}
@Override
protected void registerKeyword(String word) {
// The MariaDB driver reports that "STRING" is a keyword, but
@ -156,9 +162,9 @@ public class MariaDBDialect extends MySQLDialect {
@Override
public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry();
// Make sure we register the JSON type descriptor before calling super, because MariaDB does not need casting
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE );
jdbcTypeRegistry.addTypeConstructorIfAbsent( JsonArrayJdbcTypeConstructor.INSTANCE );
// Make sure we register the JSON type descriptor before calling super, because MariaDB needs special casting
jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, MariaDBCastingJsonJdbcType.INSTANCE );
jdbcTypeRegistry.addTypeConstructorIfAbsent( MariaDBCastingJsonArrayJdbcTypeConstructor.INSTANCE );
super.contributeTypes( typeContributions, serviceRegistry );
if ( getVersion().isSameOrAfter( 10, 7 ) ) {
@ -166,6 +172,13 @@ public class MariaDBDialect extends MySQLDialect {
}
}
@Override
public String castPattern(CastType from, CastType to) {
return to == CastType.JSON
? "json_extract(?1,'$')"
: super.castPattern( from, to );
}
@Override
public SqlAstTranslatorFactory getSqlAstTranslatorFactory() {
return new StandardSqlAstTranslatorFactory() {

View File

@ -9,6 +9,7 @@ import java.util.List;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.query.sqm.ComparisonOperator;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator;
@ -295,7 +296,54 @@ public class MariaDBSqlAstTranslator<T extends JdbcOperation> extends AbstractSq
@Override
protected void renderComparison(Expression lhs, ComparisonOperator operator, Expression rhs) {
renderComparisonDistinctOperator( lhs, operator, rhs );
final JdbcMappingContainer lhsExpressionType = lhs.getExpressionType();
if ( lhsExpressionType != null && lhsExpressionType.getJdbcTypeCount() == 1
&& lhsExpressionType.getSingleJdbcMapping().getJdbcType().isJson() ) {
switch ( operator ) {
case DISTINCT_FROM:
appendSql( "case when json_equals(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( ")=1 or " );
lhs.accept( this );
appendSql( " is null and " );
rhs.accept( this );
appendSql( " is null then 0 else 1 end=1" );
break;
case NOT_DISTINCT_FROM:
appendSql( "case when json_equals(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( ")=1 or " );
lhs.accept( this );
appendSql( " is null and " );
rhs.accept( this );
appendSql( " is null then 0 else 1 end=0" );
break;
case NOT_EQUAL:
appendSql( "json_equals(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( ")=0" );
break;
case EQUAL:
appendSql( "json_equals(" );
lhs.accept( this );
appendSql( ',' );
rhs.accept( this );
appendSql( ")=1" );
break;
default:
renderComparisonDistinctOperator( lhs, operator, rhs );
break;
}
}
else {
renderComparisonDistinctOperator( lhs, operator, rhs );
}
}
@Override

View File

@ -440,7 +440,7 @@ public class MySQLDialect extends Dialect {
@Override
public AggregateSupport getAggregateSupport() {
return MySQLAggregateSupport.valueOf( this );
return MySQLAggregateSupport.JSON_INSTANCE;
}
@Deprecated(since="6.4")

View File

@ -4,7 +4,6 @@
*/
package org.hibernate.dialect.aggregate;
import org.hibernate.dialect.Dialect;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Column;
import org.hibernate.metamodel.mapping.JdbcMapping;
@ -26,7 +25,9 @@ import static org.hibernate.type.SqlTypes.BLOB;
import static org.hibernate.type.SqlTypes.BOOLEAN;
import static org.hibernate.type.SqlTypes.CHAR;
import static org.hibernate.type.SqlTypes.CLOB;
import static org.hibernate.type.SqlTypes.DOUBLE;
import static org.hibernate.type.SqlTypes.ENUM;
import static org.hibernate.type.SqlTypes.FLOAT;
import static org.hibernate.type.SqlTypes.INTEGER;
import static org.hibernate.type.SqlTypes.JSON;
import static org.hibernate.type.SqlTypes.JSON_ARRAY;
@ -36,6 +37,7 @@ import static org.hibernate.type.SqlTypes.LONG32VARCHAR;
import static org.hibernate.type.SqlTypes.NCHAR;
import static org.hibernate.type.SqlTypes.NCLOB;
import static org.hibernate.type.SqlTypes.NVARCHAR;
import static org.hibernate.type.SqlTypes.REAL;
import static org.hibernate.type.SqlTypes.SMALLINT;
import static org.hibernate.type.SqlTypes.TIMESTAMP;
import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC;
@ -45,10 +47,13 @@ import static org.hibernate.type.SqlTypes.VARCHAR;
public class MySQLAggregateSupport extends AggregateSupportImpl {
private static final AggregateSupport INSTANCE = new MySQLAggregateSupport();
public static final AggregateSupport JSON_INSTANCE = new MySQLAggregateSupport( true );
public static final AggregateSupport LONGTEXT_INSTANCE = new MySQLAggregateSupport( false );
public static AggregateSupport valueOf(Dialect dialect) {
return MySQLAggregateSupport.INSTANCE;
private final boolean jsonType;
public MySQLAggregateSupport(boolean jsonType) {
this.jsonType = jsonType;
}
@Override
@ -72,7 +77,9 @@ public class MySQLAggregateSupport extends AggregateSupportImpl {
case BOOLEAN:
return template.replace(
placeholder,
"case " + queryExpression( aggregateParentReadExpression, columnExpression ) + " when 'true' then true when 'false' then false end"
jsonType
? "case " + queryExpression( aggregateParentReadExpression, columnExpression ) + " when cast('true' as json) then true when cast('false' as json) then false end"
: "case " + queryExpression( aggregateParentReadExpression, columnExpression ) + " when 'true' then true when 'false' then false end"
);
case BINARY:
case VARBINARY:
@ -92,12 +99,18 @@ public class MySQLAggregateSupport extends AggregateSupportImpl {
throw new IllegalArgumentException( "Unsupported aggregate SQL type: " + aggregateColumnTypeCode );
}
private static String columnCastType(SqlTypedMapping column) {
private String columnCastType(SqlTypedMapping column) {
return switch (column.getJdbcMapping().getJdbcType().getDdlTypeCode()) {
// special case for casting to Boolean
case BOOLEAN, BIT -> "unsigned";
// MySQL doesn't let you cast to INTEGER/BIGINT/TINYINT
case TINYINT, SMALLINT, INTEGER, BIGINT -> "signed";
case REAL -> "float";
case DOUBLE -> "double";
case FLOAT -> jsonType
// In newer versions of MySQL, casting to float/double is supported
? column.getColumnDefinition()
: column.getPrecision() == null || column.getPrecision() == 53 ? "double" : "float";
// MySQL doesn't let you cast to TEXT/LONGTEXT
case CHAR, VARCHAR, LONG32VARCHAR, CLOB, ENUM -> "char";
case NCHAR, NVARCHAR, LONG32NVARCHAR, NCLOB -> "char character set utf8mb4";
@ -107,12 +120,17 @@ public class MySQLAggregateSupport extends AggregateSupportImpl {
};
}
private static String valueExpression(String aggregateParentReadExpression, String columnExpression, String columnType) {
private String valueExpression(String aggregateParentReadExpression, String columnExpression, String columnType) {
return "cast(json_unquote(" + queryExpression( aggregateParentReadExpression, columnExpression ) + ") as " + columnType + ')';
}
private static String queryExpression(String aggregateParentReadExpression, String columnExpression) {
return "nullif(json_extract(" + aggregateParentReadExpression + ",'$." + columnExpression + "'),cast('null' as json))";
private String queryExpression(String aggregateParentReadExpression, String columnExpression) {
if ( jsonType ) {
return "nullif(json_extract(" + aggregateParentReadExpression + ",'$." + columnExpression + "'),cast('null' as json))";
}
else {
return "nullif(json_extract(" + aggregateParentReadExpression + ",'$." + columnExpression + "'),'null')";
}
}
private static String jsonCustomWriteExpression(String customWriteExpression, JdbcMapping jdbcMapping) {
@ -190,7 +208,7 @@ public class MySQLAggregateSupport extends AggregateSupportImpl {
SqlAstTranslator<?> translator,
AggregateColumnWriteExpression expression);
}
private static class AggregateJsonWriteExpression implements JsonWriteExpression {
private class AggregateJsonWriteExpression implements JsonWriteExpression {
private final LinkedHashMap<String, JsonWriteExpression> subExpressions = new LinkedHashMap<>();
protected void initializeSubExpressions(SelectableMapping[] columns) {
@ -242,7 +260,7 @@ public class MySQLAggregateSupport extends AggregateSupportImpl {
}
}
private static class RootJsonWriteExpression extends AggregateJsonWriteExpression
private class RootJsonWriteExpression extends AggregateJsonWriteExpression
implements WriteExpressionRenderer {
private final boolean nullable;
private final String path;

View File

@ -34,6 +34,7 @@ public enum CastType {
INTEGER, LONG, FLOAT, DOUBLE, FIXED,
DATE, TIME, TIMESTAMP,
OFFSET_TIMESTAMP, ZONE_TIMESTAMP,
JSON,
NULL,
OTHER;

View File

@ -33,9 +33,9 @@ import static org.hibernate.sql.Template.TEMPLATE;
* @author Yanming Zhou
*/
public class ColumnReference implements Expression, Assignable {
private final String qualifier;
private final @Nullable String qualifier;
private final String columnExpression;
private final SelectablePath selectablePath;
private final @Nullable SelectablePath selectablePath;
private final boolean isFormula;
private final @Nullable String readExpression;
private final JdbcMapping jdbcMapping;
@ -62,7 +62,7 @@ public class ColumnReference implements Expression, Assignable {
);
}
public ColumnReference(String qualifier, SelectableMapping selectableMapping) {
public ColumnReference(@Nullable String qualifier, SelectableMapping selectableMapping) {
this(
qualifier,
selectableMapping.getSelectionExpression(),
@ -73,7 +73,7 @@ public class ColumnReference implements Expression, Assignable {
);
}
public ColumnReference(String qualifier, SelectableMapping selectableMapping, JdbcMapping jdbcMapping) {
public ColumnReference(@Nullable String qualifier, SelectableMapping selectableMapping, JdbcMapping jdbcMapping) {
this(
qualifier,
selectableMapping.getSelectionExpression(),
@ -88,7 +88,7 @@ public class ColumnReference implements Expression, Assignable {
TableReference tableReference,
String columnExpression,
boolean isFormula,
String customReadExpression,
@Nullable String customReadExpression,
JdbcMapping jdbcMapping) {
this(
tableReference.getIdentificationVariable(),
@ -101,20 +101,20 @@ public class ColumnReference implements Expression, Assignable {
}
public ColumnReference(
String qualifier,
@Nullable String qualifier,
String columnExpression,
boolean isFormula,
String customReadExpression,
@Nullable String customReadExpression,
JdbcMapping jdbcMapping) {
this( qualifier, columnExpression, null, isFormula, customReadExpression, jdbcMapping );
}
public ColumnReference(
String qualifier,
@Nullable String qualifier,
String columnExpression,
SelectablePath selectablePath,
@Nullable SelectablePath selectablePath,
boolean isFormula,
String customReadExpression,
@Nullable String customReadExpression,
JdbcMapping jdbcMapping) {
this.qualifier = nullIfEmpty( qualifier );
@ -141,7 +141,7 @@ public class ColumnReference implements Expression, Assignable {
return this;
}
public String getQualifier() {
public @Nullable String getQualifier() {
return qualifier;
}
@ -153,11 +153,11 @@ public class ColumnReference implements Expression, Assignable {
return readExpression;
}
public String getSelectableName() {
return selectablePath.getSelectableName();
public @Nullable String getSelectableName() {
return selectablePath == null ? null : selectablePath.getSelectableName();
}
public SelectablePath getSelectablePath() {
public @Nullable SelectablePath getSelectablePath() {
return selectablePath;
}
@ -175,7 +175,7 @@ public class ColumnReference implements Expression, Assignable {
appendReadExpression( appender, qualifier );
}
public void appendReadExpression(String qualifier, Consumer<String> appender) {
public void appendReadExpression(@Nullable String qualifier, Consumer<String> appender) {
if ( isFormula ) {
appender.accept( columnExpression );
}
@ -193,7 +193,7 @@ public class ColumnReference implements Expression, Assignable {
}
}
public void appendReadExpression(SqlAppender appender, String qualifier) {
public void appendReadExpression(SqlAppender appender, @Nullable String qualifier) {
appendReadExpression( qualifier, appender::appendSql );
}
@ -201,7 +201,7 @@ public class ColumnReference implements Expression, Assignable {
appendColumnForWrite( appender, qualifier );
}
public void appendColumnForWrite(SqlAppender appender, String qualifier) {
public void appendColumnForWrite(SqlAppender appender, @Nullable String qualifier) {
if ( qualifier != null ) {
appender.append( qualifier );
appender.append( '.' );

View File

@ -4,6 +4,7 @@
*/
package org.hibernate.sql.ast.tree.expression;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.metamodel.mapping.JdbcMappingContainer;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.spi.SqlSelectionProducer;
@ -21,9 +22,9 @@ public interface Expression extends SqlAstNode, SqlSelectionProducer {
/**
* The type for this expression
*/
JdbcMappingContainer getExpressionType();
@Nullable JdbcMappingContainer getExpressionType();
default ColumnReference getColumnReference() {
default @Nullable ColumnReference getColumnReference() {
return null;
}

View File

@ -347,6 +347,9 @@ public interface JdbcType extends Serializable {
return CastType.TIMESTAMP;
case TIMESTAMP_WITH_TIMEZONE:
return CastType.OFFSET_TIMESTAMP;
case JSON:
case JSON_ARRAY:
return CastType.JSON;
case NULL:
return CastType.NULL;
default:

View File

@ -161,6 +161,50 @@ public class JsonTableTest {
} );
}
@Test
public void testArray(SessionFactoryScope scope) {
scope.inSession( em -> {
final String query = """
select
t.idx,
t.val
from json_table('[1,2]','$[*]' columns(val Integer path '$', idx for ordinality)) t
order by t.idx
""";
List<Tuple> resultList = em.createQuery( query, Tuple.class ).getResultList();
assertEquals( 2, resultList.size() );
assertEquals( 1L, resultList.get( 0 ).get( 0 ) );
assertEquals( 1, resultList.get( 0 ).get( 1 ) );
assertEquals( 2L, resultList.get( 1 ).get( 0 ) );
assertEquals( 2, resultList.get( 1 ).get( 1 ) );
} );
}
@Test
public void testArrayParam(SessionFactoryScope scope) {
scope.inSession( em -> {
final String query = """
select
t.idx,
t.val
from json_table(:arr,'$[*]' columns(val Integer path '$', idx for ordinality)) t
order by t.idx
""";
List<Tuple> resultList = em.createQuery( query, Tuple.class )
.setParameter( "arr", "[1,2]" )
.getResultList();
assertEquals( 2, resultList.size() );
assertEquals( 1L, resultList.get( 0 ).get( 0 ) );
assertEquals( 1, resultList.get( 0 ).get( 1 ) );
assertEquals( 2L, resultList.get( 1 ).get( 0 ) );
assertEquals( 2, resultList.get( 1 ).get( 1 ) );
} );
}
private static void assertTupleEquals(Tuple tuple, long arrayIndex, String arrayValue) {
assertEquals( 1, tuple.get( 0 ) );
assertEquals( 0.1F, tuple.get( 1 ) );

View File

@ -12,6 +12,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -126,6 +127,7 @@ public class BasicListTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -13,6 +13,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -127,6 +128,7 @@ public class BasicSortedSetTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -8,6 +8,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -133,6 +134,7 @@ public class BooleanArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -10,6 +10,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.PostgresPlusDialect;
import org.hibernate.dialect.SQLServerDialect;
@ -142,6 +143,7 @@ public class DateArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
@SkipForDialect(dialectClass = PostgresPlusDialect.class, reason = "Seems that comparing date[] through JDBC is buggy. ERROR: operator does not exist: timestamp without time zone[] = date[]")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {

View File

@ -9,6 +9,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -137,6 +138,7 @@ public class DoubleArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -10,6 +10,7 @@ import org.hibernate.community.dialect.DerbyDialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.MySQLDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
@ -129,6 +130,7 @@ public class EnumArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MySQLDialect.class )
@SkipForDialect(dialectClass = DerbyDialect.class )
@SkipForDialect(dialectClass = DB2Dialect.class )

View File

@ -12,6 +12,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -135,6 +136,7 @@ public class EnumSetConverterTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -12,6 +12,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -128,6 +129,7 @@ public class EnumSetTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -8,6 +8,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -125,6 +126,7 @@ public class FloatArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -8,6 +8,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -125,6 +126,7 @@ public class IntegerArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -8,6 +8,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -130,6 +131,7 @@ public class LongArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -8,6 +8,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -125,6 +126,7 @@ public class ShortArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -8,6 +8,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -125,6 +126,7 @@ public class StringArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -10,6 +10,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -139,6 +140,7 @@ public class TimeArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();

View File

@ -11,6 +11,7 @@ import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HANADialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MariaDBDialect;
import org.hibernate.dialect.OracleDialect;
import org.hibernate.dialect.SQLServerDialect;
import org.hibernate.dialect.SybaseASEDialect;
@ -144,6 +145,7 @@ public class TimestampArrayTest {
@SkipForDialect(dialectClass = SQLServerDialect.class, reason = "SQL Server requires a special function to compare XML")
@SkipForDialect(dialectClass = SybaseASEDialect.class, reason = "Sybase ASE requires a special function to compare XML")
@SkipForDialect(dialectClass = HANADialect.class, reason = "HANA requires a special function to compare LOBs")
@SkipForDialect(dialectClass = MariaDBDialect.class, reason = "MariaDB requires a special function to compare LOBs")
public void testNativeQuery(SessionFactoryScope scope) {
scope.inSession( em -> {
final Dialect dialect = em.getDialect();