diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/TimeZoneColumn.java b/hibernate-core/src/main/java/org/hibernate/annotations/TimeZoneColumn.java index ee43d6f002..343d1f61f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/TimeZoneColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/TimeZoneColumn.java @@ -68,4 +68,22 @@ public @interface TimeZoneColumn { */ String table() default ""; + /** + * (Optional) The SQL fragment that is used when + * generating the DDL for the column. + *

+ * The DDL must be written in the native SQL dialect + * of the target database (it is not portable across databases). + * + * @since 7.0 + */ + String options() default ""; + + /** + * (Optional) A comment to be applied to the column. + * + * @since 7.0 + */ + String comment() default ""; + } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java index 489384836c..0aedcf5f9f 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AbstractPropertyHolder.java @@ -504,6 +504,8 @@ public abstract class AbstractPropertyHolder implements PropertyHolder { created.nullable( column.nullable() ); if ( timeZoneColumn != null ) { + created.options( timeZoneColumn.options() ); + created.comment( timeZoneColumn.comment() ); created.table( timeZoneColumn.table() ); created.insertable( timeZoneColumn.insertable() ); created.updatable( timeZoneColumn.updatable() ); @@ -514,6 +516,8 @@ public abstract class AbstractPropertyHolder implements PropertyHolder { created.insertable( column.insertable() ); created.updatable( column.updatable() ); created.columnDefinition( column.columnDefinition() ); + created.options( column.options() ); + created.comment( column.comment() ); } return created; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java index c4517c04f2..a78de20c3c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnnotatedColumn.java @@ -99,6 +99,8 @@ public class AnnotatedColumn { private String options; + private String comment; + public AnnotatedColumns getParent() { return parent; } @@ -267,9 +269,9 @@ public class AnnotatedColumn { } mappingColumn.setOptions( options ); -// if ( isNotEmpty( comment ) ) { -// mappingColumn.setComment( comment ); -// } + if ( isNotEmpty( comment ) ) { + mappingColumn.setComment( comment ); + } if ( generatedAs != null ) { mappingColumn.setGeneratedAs( generatedAs ); } @@ -318,6 +320,7 @@ public class AnnotatedColumn { } mappingColumn.setDefaultValue( defaultValue ); mappingColumn.setOptions( options ); + mappingColumn.setComment( comment ); if ( writeExpression != null ) { final int numberOfJdbcParams = StringHelper.count( writeExpression, '?' ); @@ -814,6 +817,7 @@ public class AnnotatedColumn { annotatedColumn.applyGeneratedAs( inferredData, numberOfColumns ); annotatedColumn.applyColumnCheckConstraint( column ); annotatedColumn.applyColumnOptions( column ); + annotatedColumn.applyColumnComment(column); annotatedColumn.applyCheckConstraint( inferredData, numberOfColumns ); annotatedColumn.extractDataFromPropertyData( propertyHolder, inferredData, sourceModelContext ); annotatedColumn.bind(); @@ -1036,6 +1040,12 @@ public class AnnotatedColumn { options = column.options(); } + private void applyColumnComment(jakarta.persistence.Column column) { + if ( !column.comment().isEmpty() ) { + comment = column.comment(); + } + } + void setOptions(String options){ this.options = options; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/TimeZoneColumnAnnotation.java b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/TimeZoneColumnAnnotation.java index 69cc3c6e9f..618303e066 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/TimeZoneColumnAnnotation.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/models/annotations/internal/TimeZoneColumnAnnotation.java @@ -24,6 +24,8 @@ public class TimeZoneColumnAnnotation implements TimeZoneColumn { private boolean updatable; private String columnDefinition; private String table; + private String options; + private String comment; /** * Used in creating dynamic annotation instances (e.g. from XML) @@ -34,6 +36,8 @@ public class TimeZoneColumnAnnotation implements TimeZoneColumn { this.updatable = true; this.columnDefinition = ""; this.table = ""; + this.options = ""; + this.comment = ""; } /** @@ -45,6 +49,8 @@ public class TimeZoneColumnAnnotation implements TimeZoneColumn { this.updatable = annotation.updatable(); this.columnDefinition = annotation.columnDefinition(); this.table = annotation.table(); + this.options = annotation.options(); + this.comment = annotation.comment(); } /** @@ -71,6 +77,8 @@ public class TimeZoneColumnAnnotation implements TimeZoneColumn { modelContext ); this.table = extractJandexValue( annotation, HibernateAnnotations.TIME_ZONE_COLUMN, "table", modelContext ); + this.options = extractJandexValue( annotation, HibernateAnnotations.TIME_ZONE_COLUMN, "options", modelContext ); + this.comment = extractJandexValue( annotation, HibernateAnnotations.TIME_ZONE_COLUMN, "comment", modelContext ); } @Override @@ -127,5 +135,21 @@ public class TimeZoneColumnAnnotation implements TimeZoneColumn { this.table = value; } + @Override + public String options() { + return options; + } + public void options(String value) { + this.options = value; + } + + @Override + public String comment() { + return comment; + } + + public void comment(String value) { + this.comment = value; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/TimeZoneColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/TimeZoneColumnTest.java new file mode 100644 index 0000000000..00f2f2ae53 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/schemaupdate/TimeZoneColumnTest.java @@ -0,0 +1,139 @@ +package org.hibernate.orm.test.schemaupdate; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.time.OffsetTime; +import java.util.EnumSet; +import java.util.Locale; + +import org.hibernate.annotations.TimeZoneColumn; +import org.hibernate.annotations.TimeZoneStorage; +import org.hibernate.annotations.TimeZoneStorageType; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.hibernate.tool.schema.TargetType; + +import org.hibernate.testing.orm.junit.BaseUnitTest; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.util.ServiceRegistryUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BaseUnitTest +@JiraKey("HHH-17448") +public class TimeZoneColumnTest { + + private File output; + private StandardServiceRegistry ssr; + private MetadataImplementor metadata; + + @BeforeEach + public void setUp() throws IOException { + output = File.createTempFile( "update_script", ".sql" ); + output.deleteOnExit(); + ssr = ServiceRegistryUtil.serviceRegistry(); + } + + @AfterEach + public void tearsDown() { + output.delete(); + StandardServiceRegistryBuilder.destroy( ssr ); + } + + @Test + public void testTableCommentAreCreated() throws Exception { + createSchema( TestEntity.class ); + assertTrue( + tableCreationStatementContainsOptions( output, "birthtime_offset_offset", "option_1" ), + "TimeZoneColumn options have not been created " + ); + JdbcEnvironment jdbcEnvironment = ssr.getService( JdbcEnvironment.class ); + Dialect dialect = jdbcEnvironment.getDialect(); + if ( dialect.supportsCommentOn() ) { + assertTrue( + tableCreationStatementContainsComment( output, "birthtime_offset_offset", "This is a comment" ), + "TimeZoneColumn comment have not been created " + ); + } + } + + private void createSchema(Class... annotatedClasses) { + final MetadataSources metadataSources = new MetadataSources( ssr ); + + for ( Class c : annotatedClasses ) { + metadataSources.addAnnotatedClass( c ); + } + metadata = (MetadataImplementor) metadataSources.buildMetadata(); + metadata.orderColumns( false ); + metadata.validate(); + new SchemaExport() + .setHaltOnError( true ) + .setOutputFile( output.getAbsolutePath() ) + .setFormat( false ) + .createOnly( EnumSet.of( TargetType.SCRIPT ), metadata ); + } + + private static boolean tableCreationStatementContainsOptions( + File output, + String columnName, + String options) throws Exception { + String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .split( System.lineSeparator() ); + for ( int i = 0; i < fileContent.length; i++ ) { + String statement = fileContent[i].toUpperCase( Locale.ROOT ); + if ( statement.contains( options.toUpperCase( Locale.ROOT ) ) ) { + return true; + } + } + return false; + } + + private static boolean tableCreationStatementContainsComment( + File output, + String columnName, + String comment) throws Exception { + + String[] fileContent = new String( Files.readAllBytes( output.toPath() ) ).toLowerCase() + .split( System.lineSeparator() ); + for ( int i = 0; i < fileContent.length; i++ ) { + String statement = fileContent[i].toUpperCase( Locale.ROOT ); + if ( statement.contains( comment.toUpperCase( Locale.ROOT ) ) ) { + return true; + } + } + return false; + } + + @Entity(name = "TestEntity") + public static class TestEntity { + + @Id + private Long id; + + @TimeZoneStorage(TimeZoneStorageType.COLUMN) + @TimeZoneColumn(name = "birthtime_offset_offset", comment = "This is a comment", options = "option_1") + @Column(name = "birthtime_offset") + private OffsetTime offsetTimeColumn; + +// @TimeZoneStorage(TimeZoneStorageType.COLUMN) +// @TimeZoneColumn(name = "birthday_zoned_offset") +// @Column(name = "birthday_zoned") +// private ZonedDateTime zonedDateTimeColumn; + + } + +} +