misc javadoc and refactorings around TimeZoneStorage stuff

This commit is contained in:
Gavin 2022-12-05 20:18:23 +01:00 committed by Gavin King
parent 863dda5cf5
commit e53b55583b
16 changed files with 258 additions and 215 deletions

View File

@ -7,16 +7,20 @@
package org.hibernate; package org.hibernate;
/** /**
* Describes the storage strategies understood by Hibernate. * Enumerates the possible storage strategies for offset or zoned datetimes.
* *
* @author Christian Beikov * @author Christian Beikov
* @author Steve Ebersole * @author Steve Ebersole
* @author Andrea Boriero * @author Andrea Boriero
*
* @see org.hibernate.annotations.TimeZoneStorageType
* @see org.hibernate.dialect.TimeZoneSupport
*/ */
@Incubating @Incubating
public enum TimeZoneStorageStrategy { public enum TimeZoneStorageStrategy {
/** /**
* Stores the time zone through the "with time zone" types which retain the information. * Stores the time zone via the {@code with time zone} SQL types which retain
* the information.
*/ */
NATIVE, NATIVE,
/** /**

View File

@ -16,10 +16,11 @@ import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.METHOD;
/** /**
* Specifies the mapped column for storing the time zone information. * Specifies the mapped column for storing the time zone information,
* The annotation can be used in conjunction with the <code>TimeZoneStorageType.AUTO</code> and * for use in conjunction with {@link TimeZoneStorageType#COLUMN} or
* <code>TimeZoneStorageType.COLUMN</code>. The column is simply ignored if <code>TimeZoneStorageType.AUTO</code> * {@link TimeZoneStorageType#AUTO}. The column is simply ignored if
* is used and the database supports native time zone storage. * {@code AUTO} is used and the database supports native time zone
* storage.
* *
* @author Christian Beikov * @author Christian Beikov
* @author Steve Ebersole * @author Steve Ebersole

View File

@ -17,22 +17,27 @@ import static java.lang.annotation.ElementType.METHOD;
/** /**
* Specifies how the time zone information of a persistent property or field should be persisted. * Specifies how the time zone information of a persistent property or field should be persisted.
* The <code>TimeZoneStorage</code> annotation may be used in conjunction with the <code>Basic</code> annotation, or in * This annotation may be used in conjunction with the {@link jakarta.persistence.Basic} annotation,
* conjunction with the <code>ElementCollection</code> annotation when the * or in conjunction with the {@link jakarta.persistence.ElementCollection} annotation when the
* element collection value is of basic type. If the <code>TimeZoneStorage</code> annotation is not * element collection value is of basic type. If the {@code TimeZoneStorage} annotation is not used,
* used, the <code>TimeZoneStorageType</code> value is assumed to be <code>NORMALIZED</code>. * the {@link TimeZoneStorageType} has a default value determined by the dialect and by the
* configuration property {@value org.hibernate.cfg.AvailableSettings#TIMEZONE_DEFAULT_STORAGE}.
* *
* For example:
* <pre> * <pre>
* Example: * {@code
* @Entity public class Person {
* *
* &#064;Entity public class Person { * @TimeZoneStorage(COLUMN)
* public OffsetDateTime getBirthDateTimeNormalized() {...} * @TimeZoneColumn(column = @Column(...))
* public OffsetDateTime birthDate;
*
* @TimeZoneStorage(NATIVE)
* public OffsetDateTime registrationDate;
* *
* &#064;TimeZoneStorage
* &#064;TimeZoneColumn(column = &#064;Column(...))
* public OffsetDateTime getBirthDateTimeNativeOrColumn() {...}
* ... * ...
* } * }
*}
* </pre> * </pre>
* *
* @author Christian Beikov * @author Christian Beikov

View File

@ -11,19 +11,27 @@ import org.hibernate.dialect.Dialect;
/** /**
* Describes the storage of timezone information for zoned datetime types. * Describes the storage of timezone information for zoned datetime types.
* <p>
* A default {@code TimeZoneStorageType} may be configured explicitly using
* {@value org.hibernate.cfg.AvailableSettings#TIMEZONE_DEFAULT_STORAGE}.
* Otherwise, the storage type may be overridden for a given field or
* property of an entity using the {@link TimeZoneStorage} annotation.
* *
* @author Christian Beikov * @author Christian Beikov
* @author Steve Ebersole * @author Steve Ebersole
* @author Andrea Boriero * @author Andrea Boriero
* *
* @since 6.0 * @since 6.0
*
* @see TimeZoneStorage
* @see org.hibernate.TimeZoneStorageStrategy
*/ */
@Incubating @Incubating
public enum TimeZoneStorageType { public enum TimeZoneStorageType {
/** /**
* Stores the timezone by using the {@code with time zone} * Stores the timezone by using the {@code with time zone}
* SQL column type. * SQL column type.
* * <p>
* Error if {@link Dialect#getTimeZoneSupport()} is not * Error if {@link Dialect#getTimeZoneSupport()} is not
* {@link org.hibernate.dialect.TimeZoneSupport#NATIVE}. * {@link org.hibernate.dialect.TimeZoneSupport#NATIVE}.
*/ */
@ -34,8 +42,11 @@ public enum TimeZoneStorageType {
*/ */
NORMALIZE, NORMALIZE,
/** /**
* Does not store the time zone, and instead normalizes * Does not preserve the time zone, and instead normalizes
* timestamps to UTC. * timestamps to UTC.
* <p>
* The DDL column type depends on the setting
* {@value org.hibernate.cfg.AvailableSettings#PREFERRED_INSTANT_JDBC_TYPE}.
*/ */
NORMALIZE_UTC, NORMALIZE_UTC,
/** /**
@ -59,5 +70,4 @@ public enum TimeZoneStorageType {
* @since 6.2 * @since 6.2
*/ */
DEFAULT DEFAULT
} }

View File

@ -63,19 +63,7 @@ public class VersionResolution<E> implements BasicValue.Resolution<E> {
@Override @Override
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() { public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
if ( timeZoneStorageType != null ) { return BasicValue.timeZoneStorageStrategy( timeZoneStorageType, context );
switch ( timeZoneStorageType ) {
case COLUMN:
return TimeZoneStorageStrategy.COLUMN;
case NATIVE:
return TimeZoneStorageStrategy.NATIVE;
case NORMALIZE:
return TimeZoneStorageStrategy.NORMALIZE;
case NORMALIZE_UTC:
return TimeZoneStorageStrategy.NORMALIZE_UTC;
}
}
return context.getBuildingOptions().getDefaultTimeZoneStorage();
} }
@Override @Override

View File

@ -30,6 +30,7 @@ import org.hibernate.boot.model.source.internal.hbm.HbmMetadataSourceProcessorIm
import org.hibernate.boot.model.source.internal.hbm.MappingDocument; import org.hibernate.boot.model.source.internal.hbm.MappingDocument;
import org.hibernate.boot.model.source.internal.hbm.ModelBinder; import org.hibernate.boot.model.source.internal.hbm.ModelBinder;
import org.hibernate.boot.model.source.spi.MetadataSourceProcessor; import org.hibernate.boot.model.source.spi.MetadataSourceProcessor;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.spi.AdditionalJaxbMappingProducer; import org.hibernate.boot.spi.AdditionalJaxbMappingProducer;
import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.BootstrapContext;
@ -42,7 +43,6 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters; import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry; import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.SqlTypes; import org.hibernate.type.SqlTypes;
@ -60,6 +60,11 @@ import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.jandex.IndexView; import org.jboss.jandex.IndexView;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForArray;
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForDuration;
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForInstant;
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForUuid;
/** /**
* Represents the process of of transforming a {@link MetadataSources} * Represents the process of of transforming a {@link MetadataSources}
* reference into a {@link org.hibernate.boot.Metadata} reference. Allows for 2 different process paradigms:<ul> * reference into a {@link org.hibernate.boot.Metadata} reference. Allows for 2 different process paradigms:<ul>
@ -379,15 +384,13 @@ public class MetadataBuildingProcess {
final ClassLoaderService classLoaderService = options.getServiceRegistry().getService( ClassLoaderService.class ); final ClassLoaderService classLoaderService = options.getServiceRegistry().getService( ClassLoaderService.class );
final TypeConfiguration typeConfiguration = bootstrapContext.getTypeConfiguration(); final TypeConfiguration typeConfiguration = bootstrapContext.getTypeConfiguration();
final StandardServiceRegistry serviceRegistry = bootstrapContext.getServiceRegistry();
final JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry(); final JdbcTypeRegistry jdbcTypeRegistry = typeConfiguration.getJdbcTypeRegistry();
final TypeContributions typeContributions = () -> typeConfiguration; final TypeContributions typeContributions = () -> typeConfiguration;
// add Dialect contributed types // add Dialect contributed types
final Dialect dialect = options.getServiceRegistry().getService( JdbcServices.class ).getDialect(); final Dialect dialect = options.getServiceRegistry().getService( JdbcServices.class ).getDialect();
dialect.contributeTypes( typeContributions, options.getServiceRegistry() ); dialect.contributeTypes( typeContributions, options.getServiceRegistry() );
final JdbcType dialectUuidDescriptor = jdbcTypeRegistry.findDescriptor( SqlTypes.UUID );
final JdbcType dialectArrayDescriptor = jdbcTypeRegistry.findDescriptor( SqlTypes.ARRAY );
final JdbcType dialectIntervalDescriptor = jdbcTypeRegistry.findDescriptor( SqlTypes.INTERVAL_SECOND );
// add TypeContributor contributed types. // add TypeContributor contributed types.
for ( TypeContributor contributor : classLoaderService.loadJavaServices( TypeContributor.class ) ) { for ( TypeContributor contributor : classLoaderService.loadJavaServices( TypeContributor.class ) ) {
@ -395,51 +398,38 @@ public class MetadataBuildingProcess {
} }
// add fallback type descriptors // add fallback type descriptors
final int preferredSqlTypeCodeForUuid = ConfigurationHelper.getPreferredSqlTypeCodeForUuid( bootstrapContext.getServiceRegistry() ); final int preferredSqlTypeCodeForUuid = getPreferredSqlTypeCodeForUuid( serviceRegistry );
if ( preferredSqlTypeCodeForUuid != SqlTypes.UUID ) { if ( preferredSqlTypeCodeForUuid != SqlTypes.UUID ) {
if ( jdbcTypeRegistry.findDescriptor( SqlTypes.UUID ) == dialectUuidDescriptor ) { adaptToPreferredSqlTypeCode( jdbcTypeRegistry, SqlTypes.UUID, preferredSqlTypeCodeForUuid );
jdbcTypeRegistry.addDescriptor(
SqlTypes.UUID,
jdbcTypeRegistry.getDescriptor( preferredSqlTypeCodeForUuid )
);
}
} }
else { else {
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.UUID, SqlTypes.BINARY ); addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.UUID, SqlTypes.BINARY );
} }
jdbcTypeRegistry.addDescriptorIfAbsent( JsonJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( XmlAsStringJdbcType.INSTANCE );
final int preferredSqlTypeCodeForArray = ConfigurationHelper.getPreferredSqlTypeCodeForArray( bootstrapContext.getServiceRegistry() ); final int preferredSqlTypeCodeForArray = getPreferredSqlTypeCodeForArray( serviceRegistry );
if ( preferredSqlTypeCodeForArray != SqlTypes.ARRAY ) { if ( preferredSqlTypeCodeForArray == SqlTypes.ARRAY ) {
if ( jdbcTypeRegistry.findDescriptor( SqlTypes.ARRAY ) == dialectArrayDescriptor ) { adaptToPreferredSqlTypeCode( jdbcTypeRegistry, null, SqlTypes.ARRAY, SqlTypes.VARBINARY );
jdbcTypeRegistry.addDescriptor(
SqlTypes.ARRAY,
jdbcTypeRegistry.getDescriptor( preferredSqlTypeCodeForArray )
);
} }
else {
adaptToPreferredSqlTypeCode( jdbcTypeRegistry, SqlTypes.ARRAY, preferredSqlTypeCodeForArray );
} }
else if ( jdbcTypeRegistry.findDescriptor( SqlTypes.ARRAY ) == null ) {
// Fallback to VARBINARY final int preferredSqlTypeCodeForDuration = getPreferredSqlTypeCodeForDuration( serviceRegistry );
jdbcTypeRegistry.addDescriptor( SqlTypes.ARRAY, jdbcTypeRegistry.getDescriptor( SqlTypes.VARBINARY ) );
}
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY );
final int preferredSqlTypeCodeForDuration = ConfigurationHelper.getPreferredSqlTypeCodeForDuration( bootstrapContext.getServiceRegistry() );
if ( preferredSqlTypeCodeForDuration != SqlTypes.INTERVAL_SECOND ) { if ( preferredSqlTypeCodeForDuration != SqlTypes.INTERVAL_SECOND ) {
if ( jdbcTypeRegistry.findDescriptor( SqlTypes.INTERVAL_SECOND ) == dialectIntervalDescriptor ) { adaptToPreferredSqlTypeCode( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, preferredSqlTypeCodeForDuration );
jdbcTypeRegistry.addDescriptor(
SqlTypes.INTERVAL_SECOND,
jdbcTypeRegistry.getDescriptor( preferredSqlTypeCodeForDuration )
);
}
} }
else { else {
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.NUMERIC ); addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INTERVAL_SECOND, SqlTypes.NUMERIC );
} }
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.INET, SqlTypes.VARBINARY );
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.GEOMETRY, SqlTypes.VARBINARY ); addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.GEOMETRY, SqlTypes.VARBINARY );
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.POINT, SqlTypes.VARBINARY ); addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.POINT, SqlTypes.VARBINARY );
addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.GEOGRAPHY, SqlTypes.GEOMETRY ); addFallbackIfNecessary( jdbcTypeRegistry, SqlTypes.GEOGRAPHY, SqlTypes.GEOMETRY );
jdbcTypeRegistry.addDescriptorIfAbsent( JsonJdbcType.INSTANCE );
jdbcTypeRegistry.addDescriptorIfAbsent( XmlAsStringJdbcType.INSTANCE );
final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry(); final DdlTypeRegistry ddlTypeRegistry = typeConfiguration.getDdlTypeRegistry();
// Fallback to the biggest varchar DdlType when json is requested // Fallback to the biggest varchar DdlType when json is requested
ddlTypeRegistry.addDescriptorIfAbsent( ddlTypeRegistry.addDescriptorIfAbsent(
@ -471,21 +461,63 @@ public class MetadataBuildingProcess {
// add explicit application registered types // add explicit application registered types
typeConfiguration.addBasicTypeRegistrationContributions( options.getBasicTypeRegistrations() ); typeConfiguration.addBasicTypeRegistrationContributions( options.getBasicTypeRegistrations() );
final JdbcType timestampWithTimeZoneOverride; final JdbcType timestampWithTimeZoneOverride = getTimestampWithTimeZoneOverride( options, jdbcTypeRegistry );
switch ( options.getDefaultTimeZoneStorage() ) {
case NORMALIZE:
// For NORMALIZE, we replace the standard types that use TIMESTAMP_WITH_TIMEZONE to use TIMESTAMP
timestampWithTimeZoneOverride = jdbcTypeRegistry.getDescriptor( Types.TIMESTAMP );
break;
case NORMALIZE_UTC:
// For NORMALIZE_UTC, we replace the standard types that use TIMESTAMP_WITH_TIMEZONE to use TIMESTAMP_UTC
timestampWithTimeZoneOverride = jdbcTypeRegistry.getDescriptor( SqlTypes.TIMESTAMP_UTC );
break;
default:
timestampWithTimeZoneOverride = null;
break;
}
if ( timestampWithTimeZoneOverride != null ) { if ( timestampWithTimeZoneOverride != null ) {
adaptToDefaultTimeZoneStorage( typeConfiguration, timestampWithTimeZoneOverride );
}
final int preferredSqlTypeCodeForInstant = getPreferredSqlTypeCodeForInstant(serviceRegistry);
if ( preferredSqlTypeCodeForInstant != SqlTypes.TIMESTAMP_UTC ) {
adaptToPreferredSqlTypeCodeForInstant( typeConfiguration, jdbcTypeRegistry, preferredSqlTypeCodeForInstant );
}
}
private static void adaptToPreferredSqlTypeCode(
JdbcTypeRegistry jdbcTypeRegistry,
int defaultSqlTypeCode,
int preferredSqlTypeCode) {
adaptToPreferredSqlTypeCode(
jdbcTypeRegistry,
jdbcTypeRegistry.findDescriptor( defaultSqlTypeCode ),
defaultSqlTypeCode,
preferredSqlTypeCode
);
}
private static void adaptToPreferredSqlTypeCode(
JdbcTypeRegistry jdbcTypeRegistry,
JdbcType dialectUuidDescriptor,
int defaultSqlTypeCode,
int preferredSqlTypeCode) {
if ( jdbcTypeRegistry.findDescriptor( defaultSqlTypeCode ) == dialectUuidDescriptor ) {
jdbcTypeRegistry.addDescriptor(
defaultSqlTypeCode,
jdbcTypeRegistry.getDescriptor( preferredSqlTypeCode )
);
}
}
private static void adaptToPreferredSqlTypeCodeForInstant(
TypeConfiguration typeConfiguration,
JdbcTypeRegistry jdbcTypeRegistry,
int preferredSqlTypeCodeForInstant) {
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();
final BasicType<?> instantType = new NamedBasicTypeImpl<>(
javaTypeRegistry.getDescriptor( Instant.class ),
jdbcTypeRegistry.getDescriptor( preferredSqlTypeCodeForInstant ),
"instant"
);
basicTypeRegistry.register(
instantType,
"org.hibernate.type.InstantType",
Instant.class.getSimpleName(),
Instant.class.getName()
);
}
private static void adaptToDefaultTimeZoneStorage(
TypeConfiguration typeConfiguration,
JdbcType timestampWithTimeZoneOverride) {
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry();
final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry();
final BasicType<?> offsetDateTimeType = new NamedBasicTypeImpl<>( final BasicType<?> offsetDateTimeType = new NamedBasicTypeImpl<>(
@ -511,21 +543,17 @@ public class MetadataBuildingProcess {
ZonedDateTime.class.getName() ZonedDateTime.class.getName()
); );
} }
final int preferredSqlTypeCodeForInstant = ConfigurationHelper.getPreferredSqlTypeCodeForInstant( bootstrapContext.getServiceRegistry() );
if ( preferredSqlTypeCodeForInstant != SqlTypes.TIMESTAMP_UTC ) { private static JdbcType getTimestampWithTimeZoneOverride(MetadataBuildingOptions options, JdbcTypeRegistry jdbcTypeRegistry) {
final JavaTypeRegistry javaTypeRegistry = typeConfiguration.getJavaTypeRegistry(); switch ( options.getDefaultTimeZoneStorage() ) {
final BasicTypeRegistry basicTypeRegistry = typeConfiguration.getBasicTypeRegistry(); case NORMALIZE:
final BasicType<?> instantType = new NamedBasicTypeImpl<>( // For NORMALIZE, we replace the standard types that use TIMESTAMP_WITH_TIMEZONE to use TIMESTAMP
javaTypeRegistry.getDescriptor( Instant.class ), return jdbcTypeRegistry.getDescriptor( Types.TIMESTAMP );
jdbcTypeRegistry.getDescriptor( preferredSqlTypeCodeForInstant ), case NORMALIZE_UTC:
"instant" // For NORMALIZE_UTC, we replace the standard types that use TIMESTAMP_WITH_TIMEZONE to use TIMESTAMP_UTC
); return jdbcTypeRegistry.getDescriptor( SqlTypes.TIMESTAMP_UTC );
basicTypeRegistry.register( default:
instantType, return null;
"org.hibernate.type.InstantType",
Instant.class.getSimpleName(),
Instant.class.getName()
);
} }
} }

View File

@ -179,19 +179,7 @@ public class BasicValueBinder implements JdbcTypeIndicators {
} }
@Override @Override
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() { public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
if ( timeZoneStorageType != null ) { return BasicValue.timeZoneStorageStrategy( timeZoneStorageType, buildingContext );
switch ( timeZoneStorageType ) {
case COLUMN:
return TimeZoneStorageStrategy.COLUMN;
case NATIVE:
return TimeZoneStorageStrategy.NATIVE;
case NORMALIZE:
return TimeZoneStorageStrategy.NORMALIZE;
case NORMALIZE_UTC:
return TimeZoneStorageStrategy.NORMALIZE_UTC;
}
}
return buildingContext.getBuildingOptions().getDefaultTimeZoneStorage();
} }

View File

@ -29,7 +29,7 @@ import org.hibernate.sql.exec.spi.JdbcOperation;
import java.util.List; import java.util.List;
/** /**
* An SQL dialect for DB2 for iSeries previously known as DB2/400. * An SQL dialect for DB2 for iSeries, previously known as "DB2/400".
* *
* @author Peter DeGregorio (pdegregorio) * @author Peter DeGregorio (pdegregorio)
* @author Christian Beikov * @author Christian Beikov

View File

@ -33,7 +33,7 @@ import java.util.List;
import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE;
/** /**
* An SQL dialect for DB2 for z/OS, previously known as known as Db2 UDB for z/OS and Db2 UDB for z/OS and OS/390. * A SQL dialect for DB2 for z/OS, previously known as "Db2 UDB for z/OS" and as "Db2 UDB for z/OS and OS/390".
* *
* @author Christian Beikov * @author Christian Beikov
*/ */

View File

@ -4152,7 +4152,7 @@ public abstract class Dialect implements ConversionContext {
throw new IllegalArgumentException("scale has no meaning for floating point numbers"); throw new IllegalArgumentException("scale has no meaning for floating point numbers");
} }
// but if the user explicitly specifies a precision, we need to convert it: // but if the user explicitly specifies a precision, we need to convert it:
if (precision != null) { if ( precision != null ) {
// convert from base 10 (as specified in @Column) to base 2 (as specified by SQL) // convert from base 10 (as specified in @Column) to base 2 (as specified by SQL)
// using the magic of high school math: log_2(10^n) = n*log_2(10) = n*ln(10)/ln(2) // using the magic of high school math: log_2(10^n) = n*log_2(10) = n*ln(10)/ln(2)
precision = (int) ceil( precision * LOG_BASE2OF10 ); precision = (int) ceil( precision * LOG_BASE2OF10 );
@ -4197,8 +4197,8 @@ public abstract class Dialect implements ConversionContext {
* native datetime formatting function for this * native datetime formatting function for this
* database (often the {@code to_char()} function). * database (often the {@code to_char()} function).
* <p> * <p>
* Since it's never possible to translate all of * Since it's never possible to translate every
* the pattern letter sequences understood by * pattern letter sequences understood by
* {@code DateTimeFormatter}, only the following * {@code DateTimeFormatter}, only the following
* subset of pattern letters is accepted by * subset of pattern letters is accepted by
* Hibernate: * Hibernate:

View File

@ -9,22 +9,31 @@ package org.hibernate.dialect;
import org.hibernate.Incubating; import org.hibernate.Incubating;
/** /**
* Describes the support for "with time zone" types. * Describes the extent to which a given database supports the SQL
* {@code with time zone} types.
* <p>
* Really we only care about {@code timestamp with time zone} here,
* since the type {@code time with time zone} is deeply conceptually
* questionable, and so Hibernate eschews its use.
* *
* @author Christian Beikov * @author Christian Beikov
*/ */
@Incubating @Incubating
public enum TimeZoneSupport { public enum TimeZoneSupport {
/** /**
* The "with time zone" types retain the time zone information. * The {@code with time zone} types retain the time zone information.
* That is, a round trip writing and reading a zoned datetime results
* in the exact same zoned datetime with the same timezone.
*/ */
NATIVE, NATIVE,
/** /**
* The "with time zone" types normalize to UTC. * The {@code with time zone} types normalize to UTC. That is, a round
* trip writing and reading a zoned datetime results in a datetime
* representing the same instant, but in the timezone UTC.
*/ */
NORMALIZE, NORMALIZE,
/** /**
* No support for "with time zone" types. * No support for {@code with time zone} types.
*/ */
NONE; NONE
} }

View File

@ -12,11 +12,11 @@ import java.util.Properties;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import org.hibernate.Internal;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.TimeZoneStorageStrategy; import org.hibernate.TimeZoneStorageStrategy;
import org.hibernate.annotations.TimeZoneStorageType; import org.hibernate.annotations.TimeZoneStorageType;
import org.hibernate.boot.model.TypeDefinition; import org.hibernate.boot.model.TypeDefinition;
import org.hibernate.boot.model.TypeDefinitionRegistry;
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext; import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext;
@ -700,6 +700,13 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
@Override @Override
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() { public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
return timeZoneStorageStrategy( timeZoneStorageType, getBuildingContext() );
}
@Internal
public static TimeZoneStorageStrategy timeZoneStorageStrategy(
TimeZoneStorageType timeZoneStorageType,
MetadataBuildingContext buildingContext) {
if ( timeZoneStorageType != null ) { if ( timeZoneStorageType != null ) {
switch ( timeZoneStorageType ) { switch ( timeZoneStorageType ) {
case COLUMN: case COLUMN:
@ -712,7 +719,7 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
return TimeZoneStorageStrategy.NORMALIZE_UTC; return TimeZoneStorageStrategy.NORMALIZE_UTC;
} }
} }
return getBuildingContext().getBuildingOptions().getDefaultTimeZoneStorage(); return buildingContext.getBuildingOptions().getDefaultTimeZoneStorage();
} }
@Override @Override

View File

@ -7,13 +7,11 @@
package org.hibernate.type.descriptor.java; package org.hibernate.type.descriptor.java;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
@ -22,13 +20,13 @@ import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
/** /**
* Java type descriptor for the {@link OffsetDateTime} type. * Java type descriptor for the {@link OffsetDateTime} type.
* *
@ -52,44 +50,24 @@ public class OffsetDateTimeJavaType extends AbstractTemporalJavaType<OffsetDateT
@Override @Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators stdIndicators) { public JdbcType getRecommendedJdbcType(JdbcTypeIndicators stdIndicators) {
final TemporalType temporalPrecision = stdIndicators.getTemporalPrecision(); return stdIndicators.getTypeConfiguration().getJdbcTypeRegistry()
final JdbcTypeRegistry jdbcTypeRegistry = stdIndicators.getTypeConfiguration() .getDescriptor( stdIndicators.getDefaultZonedTimestampSqlType() );
.getJdbcTypeRegistry();
if ( temporalPrecision == null || temporalPrecision == TemporalType.TIMESTAMP ) {
switch ( stdIndicators.getDefaultTimeZoneStorageStrategy() ) {
case NORMALIZE:
return jdbcTypeRegistry.getDescriptor( Types.TIMESTAMP );
case NORMALIZE_UTC:
return jdbcTypeRegistry.getDescriptor( SqlTypes.TIMESTAMP_UTC );
default:
return jdbcTypeRegistry.getDescriptor( Types.TIMESTAMP_WITH_TIMEZONE );
}
}
switch ( temporalPrecision ) {
case TIME:
return jdbcTypeRegistry.getDescriptor( Types.TIME );
case DATE:
return jdbcTypeRegistry.getDescriptor( Types.DATE );
default:
throw new IllegalArgumentException( "Unexpected jakarta.persistence.TemporalType : " + temporalPrecision );
}
} }
@Override @Override
@SuppressWarnings("unchecked")
protected <X> TemporalJavaType<X> forTimestampPrecision(TypeConfiguration typeConfiguration) { protected <X> TemporalJavaType<X> forTimestampPrecision(TypeConfiguration typeConfiguration) {
//noinspection unchecked
return (TemporalJavaType<X>) this; return (TemporalJavaType<X>) this;
} }
@Override @Override
public String toString(OffsetDateTime value) { public String toString(OffsetDateTime value) {
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format( value ); return ISO_OFFSET_DATE_TIME.format( value );
} }
@Override @Override
public OffsetDateTime fromString(CharSequence string) { public OffsetDateTime fromString(CharSequence string) {
return OffsetDateTime.from( DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse( string ) ); return OffsetDateTime.from( ISO_OFFSET_DATE_TIME.parse( string ) );
} }
@Override @Override

View File

@ -7,13 +7,11 @@
package org.hibernate.type.descriptor.java; package org.hibernate.type.descriptor.java;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant; import java.time.Instant;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
@ -23,13 +21,13 @@ import jakarta.persistence.TemporalType;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ZonedDateTimeComparator; import org.hibernate.internal.util.ZonedDateTimeComparator;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
/** /**
* Java type descriptor for the {@link ZonedDateTime} type. * Java type descriptor for the {@link ZonedDateTime} type.
* *
@ -52,47 +50,24 @@ public class ZonedDateTimeJavaType extends AbstractTemporalJavaType<ZonedDateTim
@Override @Override
public JdbcType getRecommendedJdbcType(JdbcTypeIndicators stdIndicators) { public JdbcType getRecommendedJdbcType(JdbcTypeIndicators stdIndicators) {
final TemporalType temporalPrecision = stdIndicators.getTemporalPrecision(); return stdIndicators.getTypeConfiguration().getJdbcTypeRegistry()
final JdbcTypeRegistry jdbcTypeRegistry = stdIndicators.getTypeConfiguration() .getDescriptor( stdIndicators.getDefaultZonedTimestampSqlType() );
.getJdbcTypeRegistry();
if ( temporalPrecision == null || temporalPrecision == TemporalType.TIMESTAMP ) {
switch ( stdIndicators.getDefaultTimeZoneStorageStrategy() ) {
case NORMALIZE:
return jdbcTypeRegistry.getDescriptor( Types.TIMESTAMP );
case NORMALIZE_UTC:
return jdbcTypeRegistry.getDescriptor( SqlTypes.TIMESTAMP_UTC );
default:
return jdbcTypeRegistry.getDescriptor( Types.TIMESTAMP_WITH_TIMEZONE );
}
}
switch ( temporalPrecision ) {
case TIME: {
return jdbcTypeRegistry.getDescriptor( Types.TIME );
}
case DATE: {
return jdbcTypeRegistry.getDescriptor( Types.DATE );
}
default: {
throw new IllegalArgumentException( "Unexpected jakarta.persistence.TemporalType : " + temporalPrecision );
}
}
} }
@Override @Override
@SuppressWarnings("unchecked")
protected <X> TemporalJavaType<X> forTimestampPrecision(TypeConfiguration typeConfiguration) { protected <X> TemporalJavaType<X> forTimestampPrecision(TypeConfiguration typeConfiguration) {
//noinspection unchecked
return (TemporalJavaType<X>) this; return (TemporalJavaType<X>) this;
} }
@Override @Override
public String toString(ZonedDateTime value) { public String toString(ZonedDateTime value) {
return DateTimeFormatter.ISO_ZONED_DATE_TIME.format( value ); return ISO_ZONED_DATE_TIME.format( value );
} }
@Override @Override
public ZonedDateTime fromString(CharSequence string) { public ZonedDateTime fromString(CharSequence string) {
return ZonedDateTime.from( DateTimeFormatter.ISO_ZONED_DATE_TIME.parse( string ) ); return ZonedDateTime.from( ISO_ZONED_DATE_TIME.parse( string ) );
} }
@Override @Override

View File

@ -9,11 +9,15 @@ package org.hibernate.type.descriptor.jdbc;
import jakarta.persistence.EnumType; import jakarta.persistence.EnumType;
import jakarta.persistence.TemporalType; import jakarta.persistence.TemporalType;
import org.hibernate.AssertionFailure;
import org.hibernate.TimeZoneStorageStrategy; import org.hibernate.TimeZoneStorageStrategy;
import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import java.sql.Types;
/** /**
* A parameter object that helps determine the {@link java.sql.Types SQL/JDBC type} * A parameter object that helps determine the {@link java.sql.Types SQL/JDBC type}
* recommended by the JDBC spec (explicitly or implicitly) for a given Java type. * recommended by the JDBC spec (explicitly or implicitly) for a given Java type.
@ -123,7 +127,7 @@ public interface JdbcTypeIndicators {
/** /**
* Useful for resolutions based on column length. * Useful for resolutions based on column length.
* * <p>
* E.g. for choosing between a {@code VARCHAR} ({@code String}) and {@code CHAR(1)} ({@code Character}/{@code char}). * E.g. for choosing between a {@code VARCHAR} ({@code String}) and {@code CHAR(1)} ({@code Character}/{@code char}).
*/ */
default long getColumnLength() { default long getColumnLength() {
@ -139,7 +143,7 @@ public interface JdbcTypeIndicators {
/** /**
* Useful for resolutions based on column scale. * Useful for resolutions based on column scale.
* * <p>
* E.g. for choosing between a {@code NUMERIC} and {@code INTERVAL SECOND}. * E.g. for choosing between a {@code NUMERIC} and {@code INTERVAL SECOND}.
*/ */
default int getColumnScale() { default int getColumnScale() {
@ -174,4 +178,50 @@ public interface JdbcTypeIndicators {
private JdbcTypeIndicators getCurrentBaseSqlTypeIndicators() { private JdbcTypeIndicators getCurrentBaseSqlTypeIndicators() {
return getTypeConfiguration().getCurrentBaseSqlTypeIndicators(); return getTypeConfiguration().getCurrentBaseSqlTypeIndicators();
} }
/**
* @return the SQL column type used for storing datetimes under the
* given {@linkplain TimeZoneStorageStrategy storage strategy}
*
* @see SqlTypes#TIME_WITH_TIMEZONE
* @see SqlTypes#TIMESTAMP
* @see SqlTypes#TIMESTAMP_UTC
*/
static int getZonedTimestampSqlType(TimeZoneStorageStrategy storageStrategy) {
switch ( storageStrategy ) {
case NATIVE:
return SqlTypes.TIMESTAMP_WITH_TIMEZONE;
case COLUMN:
case NORMALIZE:
return SqlTypes.TIMESTAMP;
case NORMALIZE_UTC:
// sensitive to hibernate.type.preferred_instant_jdbc_type
return SqlTypes.TIMESTAMP_UTC;
default:
throw new AssertionFailure( "unknown time zone storage strategy" );
}
}
/**
* @return the SQL column type used for storing datetimes under the
* default {@linkplain TimeZoneStorageStrategy storage strategy}
*
* @see SqlTypes#TIME_WITH_TIMEZONE
* @see SqlTypes#TIMESTAMP
* @see SqlTypes#TIMESTAMP_UTC
*/
default int getDefaultZonedTimestampSqlType() {
final TemporalType temporalPrecision = getTemporalPrecision();
switch ( temporalPrecision == null ? TemporalType.TIMESTAMP : temporalPrecision ) {
case TIME:
return Types.TIME;
case DATE:
return Types.DATE;
case TIMESTAMP:
// sensitive to hibernate.timezone.default_storage
return getZonedTimestampSqlType( getDefaultTimeZoneStorageStrategy() );
default:
throw new IllegalArgumentException( "Unexpected jakarta.persistence.TemporalType : " + temporalPrecision);
}
}
} }

View File

@ -250,19 +250,19 @@ public class TypeConfiguration implements SessionFactoryObserver, Serializable {
} }
/** /**
* Understands the following target type names for the cast() function: * Understands the following target type names for the {@code cast()} function:
* * <ul>
* - String * <li>String
* - Character * <li>Character
* - Byte, Integer, Long * <li>Byte, Integer, Long
* - Float, Double * <li>Float, Double
* - Time, Date, Timestamp * <li>Time, Date, Timestamp
* - LocalDate, LocalTime, LocalDateTime * <li>LocalDate, LocalTime, LocalDateTime
* - BigInteger * <li>BigInteger
* - BigDecimal * <li>BigDecimal
* - Binary * <li>Binary
* - Boolean (fragile) * <li>Boolean (fragile)
* * </ul>
* (The type names are not case-sensitive.) * (The type names are not case-sensitive.)
*/ */
public BasicValuedMapping resolveCastTargetType(String name) { public BasicValuedMapping resolveCastTargetType(String name) {