HHH-16320 Handle json columns with native ddl type on H2
This commit is contained in:
parent
39f4fdda5e
commit
c3fa3ae777
|
@ -75,6 +75,7 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2
|
||||||
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
|
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
|
||||||
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
|
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
|
||||||
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
|
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
|
||||||
|
import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType;
|
||||||
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
|
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
|
||||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||||
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
|
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
|
||||||
|
@ -94,6 +95,7 @@ import static org.hibernate.type.SqlTypes.DOUBLE;
|
||||||
import static org.hibernate.type.SqlTypes.FLOAT;
|
import static org.hibernate.type.SqlTypes.FLOAT;
|
||||||
import static org.hibernate.type.SqlTypes.GEOMETRY;
|
import static org.hibernate.type.SqlTypes.GEOMETRY;
|
||||||
import static org.hibernate.type.SqlTypes.INTERVAL_SECOND;
|
import static org.hibernate.type.SqlTypes.INTERVAL_SECOND;
|
||||||
|
import static org.hibernate.type.SqlTypes.JSON;
|
||||||
import static org.hibernate.type.SqlTypes.LONG32NVARCHAR;
|
import static org.hibernate.type.SqlTypes.LONG32NVARCHAR;
|
||||||
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
|
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
|
||||||
import static org.hibernate.type.SqlTypes.LONG32VARCHAR;
|
import static org.hibernate.type.SqlTypes.LONG32VARCHAR;
|
||||||
|
@ -248,6 +250,9 @@ public class H2LegacyDialect extends Dialect {
|
||||||
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
|
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
|
||||||
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INTERVAL_SECOND, "interval second($p,$s)", this ) );
|
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INTERVAL_SECOND, "interval second($p,$s)", this ) );
|
||||||
}
|
}
|
||||||
|
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
|
||||||
|
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,6 +270,9 @@ public class H2LegacyDialect extends Dialect {
|
||||||
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
|
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
|
||||||
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
|
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
|
||||||
}
|
}
|
||||||
|
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
|
||||||
|
jdbcTypeRegistry.addDescriptorIfAbsent( H2FormatJsonJdbcType.INSTANCE );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -392,6 +400,9 @@ public class H2LegacyDialect extends Dialect {
|
||||||
if ( "GEOMETRY".equals( columnTypeName ) ) {
|
if ( "GEOMETRY".equals( columnTypeName ) ) {
|
||||||
return jdbcTypeRegistry.getDescriptor( GEOMETRY );
|
return jdbcTypeRegistry.getDescriptor( GEOMETRY );
|
||||||
}
|
}
|
||||||
|
else if ( "JSON".equals( columnTypeName ) ) {
|
||||||
|
return jdbcTypeRegistry.getDescriptor( JSON );
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return super.resolveSqlTypeDescriptor( columnTypeName, jdbcTypeCode, precision, scale, jdbcTypeRegistry );
|
return super.resolveSqlTypeDescriptor( columnTypeName, jdbcTypeCode, precision, scale, jdbcTypeRegistry );
|
||||||
|
|
|
@ -68,6 +68,7 @@ import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation;
|
||||||
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2DatabaseImpl;
|
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2DatabaseImpl;
|
||||||
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
|
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
|
||||||
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
|
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
|
||||||
|
import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType;
|
||||||
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
|
import org.hibernate.type.descriptor.jdbc.InstantJdbcType;
|
||||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||||
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
|
import org.hibernate.type.descriptor.jdbc.UUIDJdbcType;
|
||||||
|
@ -88,6 +89,7 @@ import static org.hibernate.type.SqlTypes.DOUBLE;
|
||||||
import static org.hibernate.type.SqlTypes.FLOAT;
|
import static org.hibernate.type.SqlTypes.FLOAT;
|
||||||
import static org.hibernate.type.SqlTypes.GEOMETRY;
|
import static org.hibernate.type.SqlTypes.GEOMETRY;
|
||||||
import static org.hibernate.type.SqlTypes.INTERVAL_SECOND;
|
import static org.hibernate.type.SqlTypes.INTERVAL_SECOND;
|
||||||
|
import static org.hibernate.type.SqlTypes.JSON;
|
||||||
import static org.hibernate.type.SqlTypes.LONG32NVARCHAR;
|
import static org.hibernate.type.SqlTypes.LONG32NVARCHAR;
|
||||||
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
|
import static org.hibernate.type.SqlTypes.LONG32VARBINARY;
|
||||||
import static org.hibernate.type.SqlTypes.LONG32VARCHAR;
|
import static org.hibernate.type.SqlTypes.LONG32VARCHAR;
|
||||||
|
@ -229,6 +231,9 @@ public class H2Dialect extends Dialect {
|
||||||
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
|
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
|
||||||
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INTERVAL_SECOND, "interval second($p,$s)", this ) );
|
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INTERVAL_SECOND, "interval second($p,$s)", this ) );
|
||||||
}
|
}
|
||||||
|
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
|
||||||
|
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -243,6 +248,9 @@ public class H2Dialect extends Dialect {
|
||||||
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
|
if ( getVersion().isSameOrAfter( 1, 4, 198 ) ) {
|
||||||
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
|
jdbcTypeRegistry.addDescriptorIfAbsent( H2DurationIntervalSecondJdbcType.INSTANCE );
|
||||||
}
|
}
|
||||||
|
if ( getVersion().isSameOrAfter( 1, 4, 200 ) ) {
|
||||||
|
jdbcTypeRegistry.addDescriptorIfAbsent( H2FormatJsonJdbcType.INSTANCE );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -370,6 +378,9 @@ public class H2Dialect extends Dialect {
|
||||||
if ( "GEOMETRY".equals( columnTypeName ) ) {
|
if ( "GEOMETRY".equals( columnTypeName ) ) {
|
||||||
return jdbcTypeRegistry.getDescriptor( GEOMETRY );
|
return jdbcTypeRegistry.getDescriptor( GEOMETRY );
|
||||||
}
|
}
|
||||||
|
else if ( "JSON".equals( columnTypeName ) ) {
|
||||||
|
return jdbcTypeRegistry.getDescriptor( JSON );
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return super.resolveSqlTypeDescriptor( columnTypeName, jdbcTypeCode, precision, scale, jdbcTypeRegistry );
|
return super.resolveSqlTypeDescriptor( columnTypeName, jdbcTypeCode, precision, scale, jdbcTypeRegistry );
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.type.descriptor.jdbc;
|
||||||
|
|
||||||
|
import org.hibernate.dialect.Dialect;
|
||||||
|
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
|
||||||
|
import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
|
||||||
|
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized type mapping for {@code JSON} that utilizes the custom
|
||||||
|
* '{@code ? format json}' write expression for H2.
|
||||||
|
*
|
||||||
|
* @author Marco Belladelli
|
||||||
|
*/
|
||||||
|
public class H2FormatJsonJdbcType extends JsonJdbcType {
|
||||||
|
/**
|
||||||
|
* Singleton access
|
||||||
|
*/
|
||||||
|
public static final H2FormatJsonJdbcType INSTANCE = new H2FormatJsonJdbcType( null );
|
||||||
|
|
||||||
|
protected H2FormatJsonJdbcType(EmbeddableMappingType embeddableMappingType) {
|
||||||
|
super( embeddableMappingType );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "FormatJsonJdbcType";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AggregateJdbcType resolveAggregateJdbcType(
|
||||||
|
EmbeddableMappingType mappingType,
|
||||||
|
String sqlType,
|
||||||
|
RuntimeModelCreationContext creationContext) {
|
||||||
|
return new H2FormatJsonJdbcType( mappingType );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appendWriteExpression(String writeExpression, SqlAppender appender, Dialect dialect) {
|
||||||
|
appender.append( writeExpression );
|
||||||
|
appender.append( " format json" );
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* 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.orm.test.type;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.JdbcTypeCode;
|
||||||
|
import org.hibernate.dialect.H2Dialect;
|
||||||
|
import org.hibernate.type.SqlTypes;
|
||||||
|
|
||||||
|
import org.hibernate.testing.orm.junit.DomainModel;
|
||||||
|
import org.hibernate.testing.orm.junit.Jira;
|
||||||
|
import org.hibernate.testing.orm.junit.RequiresDialect;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||||
|
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@SessionFactory
|
||||||
|
@DomainModel( annotatedClasses = { H2JsonListTest.Path.class, H2JsonListTest.PathClob.class } )
|
||||||
|
@RequiresDialect( H2Dialect.class )
|
||||||
|
@Jira( "https://hibernate.atlassian.net/browse/HHH-16320" )
|
||||||
|
public class H2JsonListTest {
|
||||||
|
@BeforeAll
|
||||||
|
public void setUp(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
session.persist( new Path( List.of( UUID.randomUUID(), UUID.randomUUID() ) ) );
|
||||||
|
session.persist( new PathClob( List.of( UUID.randomUUID(), UUID.randomUUID() ) ) );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public void tearDown(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
session.createMutationQuery( "delete from Path" ).executeUpdate();
|
||||||
|
session.createMutationQuery( "delete from PathClob" ).executeUpdate();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRetrievalJson(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
final Path path = session.find( Path.class, 1L );
|
||||||
|
assertThat( path ).isNotNull();
|
||||||
|
assertThat( path.getRelativePaths() ).hasSize( 2 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNativeSyntaxJson(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
session.createNativeMutationQuery( "insert into paths (relativePaths,id) values (?1 FORMAT JSON, ?2)" )
|
||||||
|
.setParameter(
|
||||||
|
1,
|
||||||
|
"[\"2b099c92-95ff-42e0-9f8c-f08c2518792d\", \"8d2164db-86b4-460a-91d0-bf821a8ca3d7\"]"
|
||||||
|
)
|
||||||
|
.setParameter( 2, 99L )
|
||||||
|
.executeUpdate();
|
||||||
|
} );
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
final Path path = session.createNativeQuery(
|
||||||
|
"select * from paths_clob where id = 99",
|
||||||
|
Path.class
|
||||||
|
).getSingleResult();
|
||||||
|
assertThat( path ).isNotNull();
|
||||||
|
assertThat( path.getRelativePaths() ).hasSize( 2 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRetrievalClob(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
final PathClob path = session.find( PathClob.class, 1L );
|
||||||
|
assertThat( path ).isNotNull();
|
||||||
|
assertThat( path.getRelativePaths() ).hasSize( 2 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNativeSyntaxClob(SessionFactoryScope scope) {
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
session.createNativeMutationQuery( "insert into paths_clob (relativePaths,id) values (?1 FORMAT JSON, ?2)" )
|
||||||
|
.setParameter(
|
||||||
|
1,
|
||||||
|
"[\"2b099c92-95ff-42e0-9f8c-f08c2518792d\", \"8d2164db-86b4-460a-91d0-bf821a8ca3d7\"]"
|
||||||
|
)
|
||||||
|
.setParameter( 2, 99L )
|
||||||
|
.executeUpdate();
|
||||||
|
} );
|
||||||
|
scope.inTransaction( session -> {
|
||||||
|
final PathClob path = session.createNativeQuery(
|
||||||
|
"select * from paths_clob where id = 99",
|
||||||
|
PathClob.class
|
||||||
|
).getSingleResult();
|
||||||
|
assertThat( path ).isNotNull();
|
||||||
|
assertThat( path.getRelativePaths() ).hasSize( 2 );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity( name = "Path" )
|
||||||
|
@Table( name = "paths" )
|
||||||
|
public static class Path {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@JdbcTypeCode( SqlTypes.JSON )
|
||||||
|
public List<UUID> relativePaths;
|
||||||
|
|
||||||
|
public Path() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path(List<UUID> relativePaths) {
|
||||||
|
this.relativePaths = relativePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UUID> getRelativePaths() {
|
||||||
|
return relativePaths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity( name = "PathClob" )
|
||||||
|
@Table( name = "paths_clob" )
|
||||||
|
public static class PathClob {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@JdbcTypeCode( SqlTypes.JSON )
|
||||||
|
@Column( columnDefinition = "clob" )
|
||||||
|
public List<UUID> relativePaths;
|
||||||
|
|
||||||
|
public PathClob() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public PathClob(List<UUID> relativePaths) {
|
||||||
|
this.relativePaths = relativePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<UUID> getRelativePaths() {
|
||||||
|
return relativePaths;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue