HHH-17575 - Add a new @FractionalSeconds annotation
This commit is contained in:
parent
53dbc959e1
commit
480072d4d1
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.annotations;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* Indicates that the associated temporal value should be stored with fractional seconds.
|
||||
* Only valid for values which contain seconds.
|
||||
*
|
||||
* @apiNote The presence or absence of this annotation implies different semantics for time
|
||||
* versus timestamp based values. By default, time values are stored without fractional seconds
|
||||
* whereas timestamp values are stored with a precision based on the
|
||||
* {@linkplain org.hibernate.dialect.Dialect#getDefaultTimestampPrecision Dialect default}
|
||||
*
|
||||
* @see java.time.Instant
|
||||
* @see java.time.LocalDateTime
|
||||
* @see java.time.LocalTime
|
||||
* @see java.time.OffsetDateTime
|
||||
* @see java.time.OffsetTime
|
||||
* @see java.time.ZonedDateTime
|
||||
* @see java.sql.Time
|
||||
* @see java.sql.Timestamp
|
||||
* @see java.util.Calendar
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@Target({METHOD, FIELD})
|
||||
@Retention( RUNTIME)
|
||||
@Incubating
|
||||
public @interface FractionalSeconds {
|
||||
/**
|
||||
* The fractional precision for the associated seconds. Generally this will be one of<ul>
|
||||
* <li>3 (milliseconds)</li>
|
||||
* <li>6 (microseconds)</li>
|
||||
* <li>9 (nanoseconds)</li>
|
||||
* </ul>
|
||||
*/
|
||||
int value();
|
||||
}
|
|
@ -17,6 +17,7 @@ import org.hibernate.annotations.Checks;
|
|||
import org.hibernate.annotations.ColumnDefault;
|
||||
import org.hibernate.annotations.ColumnTransformer;
|
||||
import org.hibernate.annotations.ColumnTransformers;
|
||||
import org.hibernate.annotations.FractionalSeconds;
|
||||
import org.hibernate.annotations.GeneratedColumn;
|
||||
import org.hibernate.annotations.Index;
|
||||
import org.hibernate.annotations.common.reflection.XProperty;
|
||||
|
@ -77,6 +78,7 @@ public class AnnotatedColumn {
|
|||
private Long length;
|
||||
private Integer precision;
|
||||
private Integer scale;
|
||||
private Integer temporalPrecision; // technically scale, but most dbs call it precision so...
|
||||
private Integer arrayLength;
|
||||
private String logicalColumnName;
|
||||
private boolean unique;
|
||||
|
@ -183,6 +185,10 @@ public class AnnotatedColumn {
|
|||
this.scale = scale;
|
||||
}
|
||||
|
||||
public void setTemporalPrecision(Integer temporalPrecision) {
|
||||
this.temporalPrecision = temporalPrecision;
|
||||
}
|
||||
|
||||
public void setLogicalColumnName(String logicalColumnName) {
|
||||
this.logicalColumnName = logicalColumnName;
|
||||
}
|
||||
|
@ -239,6 +245,7 @@ public class AnnotatedColumn {
|
|||
length,
|
||||
precision,
|
||||
scale,
|
||||
temporalPrecision,
|
||||
arrayLength,
|
||||
nullable,
|
||||
sqlType,
|
||||
|
@ -269,6 +276,7 @@ public class AnnotatedColumn {
|
|||
Long length,
|
||||
Integer precision,
|
||||
Integer scale,
|
||||
Integer temporalPrecision,
|
||||
Integer arrayLength,
|
||||
boolean nullable,
|
||||
String sqlType,
|
||||
|
@ -287,6 +295,9 @@ public class AnnotatedColumn {
|
|||
mappingColumn.setPrecision( precision );
|
||||
mappingColumn.setScale( scale );
|
||||
}
|
||||
if ( temporalPrecision != null ) {
|
||||
mappingColumn.setTemporalPrecision( temporalPrecision );
|
||||
}
|
||||
mappingColumn.setArrayLength( arrayLength );
|
||||
mappingColumn.setNullable( nullable );
|
||||
mappingColumn.setSqlType( sqlType );
|
||||
|
@ -488,6 +499,7 @@ public class AnnotatedColumn {
|
|||
return buildColumnOrFormulaFromAnnotation(
|
||||
null,
|
||||
formulaAnn,
|
||||
null,
|
||||
// commentAnn,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -498,6 +510,7 @@ public class AnnotatedColumn {
|
|||
}
|
||||
|
||||
public static AnnotatedColumns buildColumnFromNoAnnotation(
|
||||
FractionalSeconds fractionalSeconds,
|
||||
// Comment commentAnn,
|
||||
Nullability nullability,
|
||||
PropertyHolder propertyHolder,
|
||||
|
@ -506,6 +519,7 @@ public class AnnotatedColumn {
|
|||
MetadataBuildingContext context) {
|
||||
return buildColumnsFromAnnotations(
|
||||
null,
|
||||
fractionalSeconds,
|
||||
// commentAnn,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -517,6 +531,7 @@ public class AnnotatedColumn {
|
|||
|
||||
public static AnnotatedColumns buildColumnFromAnnotation(
|
||||
jakarta.persistence.Column column,
|
||||
org.hibernate.annotations.FractionalSeconds fractionalSeconds,
|
||||
// Comment commentAnn,
|
||||
Nullability nullability,
|
||||
PropertyHolder propertyHolder,
|
||||
|
@ -526,6 +541,7 @@ public class AnnotatedColumn {
|
|||
return buildColumnOrFormulaFromAnnotation(
|
||||
column,
|
||||
null,
|
||||
fractionalSeconds,
|
||||
// commentAnn,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -537,6 +553,7 @@ public class AnnotatedColumn {
|
|||
|
||||
public static AnnotatedColumns buildColumnsFromAnnotations(
|
||||
jakarta.persistence.Column[] columns,
|
||||
FractionalSeconds fractionalSeconds,
|
||||
// Comment commentAnn,
|
||||
Nullability nullability,
|
||||
PropertyHolder propertyHolder,
|
||||
|
@ -546,6 +563,7 @@ public class AnnotatedColumn {
|
|||
return buildColumnsOrFormulaFromAnnotation(
|
||||
columns,
|
||||
null,
|
||||
fractionalSeconds,
|
||||
// commentAnn,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -568,6 +586,7 @@ public class AnnotatedColumn {
|
|||
return buildColumnsOrFormulaFromAnnotation(
|
||||
columns,
|
||||
null,
|
||||
null,
|
||||
// commentAnn,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -581,6 +600,7 @@ public class AnnotatedColumn {
|
|||
public static AnnotatedColumns buildColumnOrFormulaFromAnnotation(
|
||||
jakarta.persistence.Column column,
|
||||
org.hibernate.annotations.Formula formulaAnn,
|
||||
org.hibernate.annotations.FractionalSeconds fractionalSeconds,
|
||||
// Comment commentAnn,
|
||||
Nullability nullability,
|
||||
PropertyHolder propertyHolder,
|
||||
|
@ -590,6 +610,7 @@ public class AnnotatedColumn {
|
|||
return buildColumnsOrFormulaFromAnnotation(
|
||||
column==null ? null : new jakarta.persistence.Column[] { column },
|
||||
formulaAnn,
|
||||
fractionalSeconds,
|
||||
// commentAnn,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -603,6 +624,7 @@ public class AnnotatedColumn {
|
|||
public static AnnotatedColumns buildColumnsOrFormulaFromAnnotation(
|
||||
jakarta.persistence.Column[] columns,
|
||||
org.hibernate.annotations.Formula formulaAnn,
|
||||
org.hibernate.annotations.FractionalSeconds fractionalSeconds,
|
||||
// Comment comment,
|
||||
Nullability nullability,
|
||||
PropertyHolder propertyHolder,
|
||||
|
@ -630,6 +652,7 @@ public class AnnotatedColumn {
|
|||
final jakarta.persistence.Column[] actualColumns = overrideColumns( columns, propertyHolder, inferredData );
|
||||
if ( actualColumns == null ) {
|
||||
return buildImplicitColumn(
|
||||
fractionalSeconds,
|
||||
inferredData,
|
||||
suffixForDefaultColumnName,
|
||||
secondaryTables,
|
||||
|
@ -647,7 +670,8 @@ public class AnnotatedColumn {
|
|||
suffixForDefaultColumnName,
|
||||
secondaryTables,
|
||||
context,
|
||||
actualColumns
|
||||
actualColumns,
|
||||
fractionalSeconds
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -684,7 +708,8 @@ public class AnnotatedColumn {
|
|||
String suffixForDefaultColumnName,
|
||||
Map<String, Join> secondaryTables,
|
||||
MetadataBuildingContext context,
|
||||
jakarta.persistence.Column[] actualCols) {
|
||||
jakarta.persistence.Column[] actualCols,
|
||||
FractionalSeconds fractionalSeconds) {
|
||||
final AnnotatedColumns parent = new AnnotatedColumns();
|
||||
parent.setPropertyHolder( propertyHolder );
|
||||
parent.setPropertyName( getRelativePath( propertyHolder, inferredData.getPropertyName() ) );
|
||||
|
@ -708,6 +733,7 @@ public class AnnotatedColumn {
|
|||
actualCols.length,
|
||||
database,
|
||||
column,
|
||||
fractionalSeconds,
|
||||
sqlType,
|
||||
tableName
|
||||
);
|
||||
|
@ -734,6 +760,7 @@ public class AnnotatedColumn {
|
|||
int numberOfColumns,
|
||||
Database database,
|
||||
jakarta.persistence.Column column,
|
||||
FractionalSeconds fractionalSeconds,
|
||||
String sqlType,
|
||||
String tableName) {
|
||||
final String columnName = logicalColumnName( inferredData, suffixForDefaultColumnName, database, column );
|
||||
|
@ -742,7 +769,12 @@ public class AnnotatedColumn {
|
|||
annotatedColumn.setImplicit( false );
|
||||
annotatedColumn.setSqlType( sqlType );
|
||||
annotatedColumn.setLength( (long) column.length() );
|
||||
annotatedColumn.setPrecision( column.precision() );
|
||||
if ( fractionalSeconds != null ) {
|
||||
annotatedColumn.setTemporalPrecision( fractionalSeconds.value() );
|
||||
}
|
||||
else {
|
||||
annotatedColumn.setPrecision( column.precision() );
|
||||
}
|
||||
annotatedColumn.setScale( column.scale() );
|
||||
annotatedColumn.handleArrayLength( inferredData );
|
||||
// annotatedColumn.setPropertyHolder( propertyHolder );
|
||||
|
@ -881,6 +913,7 @@ public class AnnotatedColumn {
|
|||
}
|
||||
|
||||
private static AnnotatedColumns buildImplicitColumn(
|
||||
FractionalSeconds fractionalSeconds,
|
||||
PropertyData inferredData,
|
||||
String suffixForDefaultColumnName,
|
||||
Map<String, Join> secondaryTables,
|
||||
|
@ -920,6 +953,9 @@ public class AnnotatedColumn {
|
|||
column.applyCheckConstraint( inferredData, 1 );
|
||||
column.extractDataFromPropertyData( propertyHolder, inferredData );
|
||||
column.handleArrayLength( inferredData );
|
||||
if ( fractionalSeconds != null ) {
|
||||
column.setTemporalPrecision( fractionalSeconds.value() );
|
||||
}
|
||||
column.bind();
|
||||
return columns;
|
||||
}
|
||||
|
|
|
@ -325,6 +325,7 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
|
|||
referencedColumn.getLength(),
|
||||
referencedColumn.getPrecision(),
|
||||
referencedColumn.getScale(),
|
||||
referencedColumn.getTemporalPrecision(),
|
||||
referencedColumn.getArrayLength(),
|
||||
mappingColumn != null && mappingColumn.isNullable(),
|
||||
referencedColumn.getSqlType(),
|
||||
|
@ -375,6 +376,7 @@ public class AnnotatedJoinColumn extends AnnotatedColumn {
|
|||
column.getLength(),
|
||||
column.getPrecision(),
|
||||
column.getScale(),
|
||||
column.getTemporalPrecision(),
|
||||
column.getArrayLength(),
|
||||
getMappingColumn().isNullable(),
|
||||
column.getSqlType(),
|
||||
|
|
|
@ -761,6 +761,7 @@ public class BinderHelper {
|
|||
final AnnotatedColumns discriminatorColumns = buildColumnOrFormulaFromAnnotation(
|
||||
discriminatorColumn,
|
||||
discriminatorFormula,
|
||||
null,
|
||||
// null,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
|
|
@ -570,6 +570,7 @@ public abstract class CollectionBinder {
|
|||
if ( property.isAnnotationPresent( jakarta.persistence.Column.class ) ) {
|
||||
return buildColumnFromAnnotation(
|
||||
property.getAnnotation( jakarta.persistence.Column.class ),
|
||||
null,
|
||||
// comment,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -592,6 +593,7 @@ public abstract class CollectionBinder {
|
|||
else if ( property.isAnnotationPresent( Columns.class ) ) {
|
||||
return buildColumnsFromAnnotations(
|
||||
property.getAnnotation( Columns.class ).columns(),
|
||||
null,
|
||||
// comment,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -602,6 +604,7 @@ public abstract class CollectionBinder {
|
|||
}
|
||||
else {
|
||||
return buildColumnFromNoAnnotation(
|
||||
null,
|
||||
// comment,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.boot.model.internal;
|
|||
import org.hibernate.AnnotationException;
|
||||
import org.hibernate.annotations.Columns;
|
||||
import org.hibernate.annotations.Formula;
|
||||
import org.hibernate.annotations.FractionalSeconds;
|
||||
import org.hibernate.annotations.JoinColumnOrFormula;
|
||||
import org.hibernate.annotations.JoinColumnsOrFormulas;
|
||||
import org.hibernate.annotations.JoinFormula;
|
||||
|
@ -85,6 +86,7 @@ class ColumnsBuilder {
|
|||
if ( property.isAnnotationPresent( Column.class ) ) {
|
||||
columns = buildColumnFromAnnotation(
|
||||
property.getAnnotation( Column.class ),
|
||||
property.getAnnotation( FractionalSeconds.class ),
|
||||
// comment,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -107,6 +109,7 @@ class ColumnsBuilder {
|
|||
else if ( property.isAnnotationPresent( Columns.class ) ) {
|
||||
columns = buildColumnsFromAnnotations(
|
||||
property.getAnnotation( Columns.class ).columns(),
|
||||
null,
|
||||
// comment,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
@ -144,6 +147,7 @@ class ColumnsBuilder {
|
|||
if ( columns == null && !property.isAnnotationPresent( ManyToMany.class ) ) {
|
||||
//useful for collection of embedded elements
|
||||
columns = buildColumnFromNoAnnotation(
|
||||
property.getAnnotation( FractionalSeconds.class ),
|
||||
// comment,
|
||||
nullability,
|
||||
propertyHolder,
|
||||
|
|
|
@ -70,6 +70,7 @@ public class IdBagBinder extends BagBinder {
|
|||
final AnnotatedColumns idColumns = AnnotatedColumn.buildColumnsFromAnnotations(
|
||||
new Column[] { collectionIdAnn.column() },
|
||||
// null,
|
||||
null,
|
||||
Nullability.FORCED_NOT_NULL,
|
||||
propertyHolder,
|
||||
propertyData,
|
||||
|
|
|
@ -63,7 +63,6 @@ import org.hibernate.type.descriptor.java.BasicJavaType;
|
|||
import org.hibernate.type.descriptor.java.BasicPluralJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.MutabilityPlan;
|
||||
import org.hibernate.type.descriptor.java.spi.FormatMapperBasedJavaType;
|
||||
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
|
||||
import org.hibernate.type.descriptor.java.spi.JsonJavaType;
|
||||
import org.hibernate.type.descriptor.java.spi.RegistryHelper;
|
||||
|
@ -233,9 +232,10 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
|
|||
|
||||
@Override
|
||||
public long getColumnLength() {
|
||||
final Selectable column = getColumn();
|
||||
if ( column instanceof Column ) {
|
||||
final Long length = ( (Column) column ).getLength();
|
||||
final Selectable selectable = getColumn();
|
||||
if ( selectable instanceof Column ) {
|
||||
final Column column = (Column) selectable;
|
||||
final Long length = column.getLength();
|
||||
return length == null ? NO_COLUMN_LENGTH : length;
|
||||
}
|
||||
else {
|
||||
|
@ -245,10 +245,14 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
|
|||
|
||||
@Override
|
||||
public int getColumnPrecision() {
|
||||
final Selectable column = getColumn();
|
||||
if ( column instanceof Column ) {
|
||||
final Integer length = ( (Column) column ).getPrecision();
|
||||
return length == null ? NO_COLUMN_PRECISION : length;
|
||||
final Selectable selectable = getColumn();
|
||||
if ( selectable instanceof Column ) {
|
||||
final Column column = (Column) selectable;
|
||||
if ( column.getTemporalPrecision() != null ) {
|
||||
return column.getTemporalPrecision();
|
||||
}
|
||||
final Integer precision = column.getPrecision();
|
||||
return precision == null ? NO_COLUMN_PRECISION : precision;
|
||||
}
|
||||
else {
|
||||
return NO_COLUMN_PRECISION;
|
||||
|
@ -257,10 +261,11 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
|
|||
|
||||
@Override
|
||||
public int getColumnScale() {
|
||||
final Selectable column = getColumn();
|
||||
if ( column instanceof Column ) {
|
||||
final Integer length = ( (Column) column ).getScale();
|
||||
return length == null ? NO_COLUMN_SCALE : length;
|
||||
final Selectable selectable = getColumn();
|
||||
if ( selectable instanceof Column ) {
|
||||
final Column column = (Column) selectable;
|
||||
final Integer scale = column.getScale();
|
||||
return scale == null ? NO_COLUMN_SCALE : scale;
|
||||
}
|
||||
else {
|
||||
return NO_COLUMN_SCALE;
|
||||
|
|
|
@ -23,12 +23,15 @@ import org.hibernate.engine.spi.Mapping;
|
|||
import org.hibernate.loader.internal.AliasConstantsHelper;
|
||||
import org.hibernate.metamodel.mapping.JdbcMapping;
|
||||
import org.hibernate.query.sqm.function.SqmFunctionRegistry;
|
||||
import org.hibernate.query.sqm.internal.TypecheckUtil;
|
||||
import org.hibernate.sql.Template;
|
||||
import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.ComponentType;
|
||||
import org.hibernate.type.EntityType;
|
||||
import org.hibernate.type.Type;
|
||||
import org.hibernate.type.descriptor.JdbcTypeNameMapper;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeHelper;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.descriptor.sql.DdlType;
|
||||
import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry;
|
||||
|
@ -51,6 +54,7 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
|
|||
private Long length;
|
||||
private Integer precision;
|
||||
private Integer scale;
|
||||
private Integer temporalPrecision;
|
||||
private Integer arrayLength;
|
||||
private Value value;
|
||||
private int typeIndex;
|
||||
|
@ -422,12 +426,23 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
|
|||
|
||||
Size calculateColumnSize(Dialect dialect, Mapping mapping) {
|
||||
Type type = getValue().getType();
|
||||
Long lengthToUse = getLength();
|
||||
Integer precisionToUse = getPrecision();
|
||||
Integer scaleToUse = getScale();
|
||||
if ( type instanceof EntityType ) {
|
||||
type = getTypeForEntityValue( mapping, type, getTypeIndex() );
|
||||
}
|
||||
if ( type instanceof ComponentType ) {
|
||||
type = getTypeForComponentValue( mapping, type, getTypeIndex() );
|
||||
}
|
||||
if ( type instanceof BasicType ) {
|
||||
final BasicType<?> basicType = (BasicType<?>) type;
|
||||
if ( JavaTypeHelper.isTemporal( basicType.getExpressibleJavaType() ) ) {
|
||||
precisionToUse = getTemporalPrecision();
|
||||
lengthToUse = null;
|
||||
scaleToUse = null;
|
||||
}
|
||||
}
|
||||
if ( type == null ) {
|
||||
throw new AssertionFailure( "no typing information available to determine column size" );
|
||||
}
|
||||
|
@ -435,9 +450,9 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
|
|||
Size size = dialect.getSizeStrategy().resolveSize(
|
||||
jdbcMapping.getJdbcType(),
|
||||
jdbcMapping.getJdbcJavaType(),
|
||||
precision,
|
||||
scale,
|
||||
length
|
||||
precisionToUse,
|
||||
scaleToUse,
|
||||
lengthToUse
|
||||
);
|
||||
size.setArrayLength( arrayLength );
|
||||
return size;
|
||||
|
@ -674,6 +689,14 @@ public class Column implements Selectable, Serializable, Cloneable, ColumnTypeIn
|
|||
this.scale = scale;
|
||||
}
|
||||
|
||||
public Integer getTemporalPrecision() {
|
||||
return temporalPrecision;
|
||||
}
|
||||
|
||||
public void setTemporalPrecision(Integer temporalPrecision) {
|
||||
this.temporalPrecision = temporalPrecision;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
|
|
@ -463,7 +463,19 @@ public final class DateTimeUtils {
|
|||
);
|
||||
}
|
||||
|
||||
public static <T extends Temporal> T roundToSecondPrecision(T temporal, int precision) {
|
||||
//noinspection unchecked
|
||||
return (T) temporal.with(
|
||||
ChronoField.NANO_OF_SECOND,
|
||||
roundToPrecision( temporal.get( ChronoField.NANO_OF_SECOND ), precision )
|
||||
);
|
||||
}
|
||||
|
||||
public static long roundToPrecision(int nano, int precision) {
|
||||
assert precision < 9 : "Precision (scale) should be less-than 9 - " + precision;
|
||||
if ( precision == 0 ) {
|
||||
return 0;
|
||||
}
|
||||
final int precisionMask = pow10( 9 - precision );
|
||||
final int nanosToRound = nano % precisionMask;
|
||||
return nano - nanosToRound + ( nanosToRound >= ( precisionMask >> 1 ) ? precisionMask : 0 );
|
||||
|
@ -478,17 +490,17 @@ public final class DateTimeUtils {
|
|||
case 2:
|
||||
return 100;
|
||||
case 3:
|
||||
return 1000;
|
||||
return 1_000;
|
||||
case 4:
|
||||
return 10000;
|
||||
return 10_000;
|
||||
case 5:
|
||||
return 100000;
|
||||
return 100_000;
|
||||
case 6:
|
||||
return 1000000;
|
||||
return 1_000_000;
|
||||
case 7:
|
||||
return 10000000;
|
||||
return 10_000_000;
|
||||
case 8:
|
||||
return 100000000;
|
||||
return 100_000_000;
|
||||
default:
|
||||
return (int) Math.pow( 10, exponent );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* 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.temporal;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.OffsetTime;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.hibernate.annotations.FractionalSeconds;
|
||||
import org.hibernate.boot.spi.MetadataImplementor;
|
||||
import org.hibernate.dialect.DB2Dialect;
|
||||
import org.hibernate.dialect.DerbyDialect;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
import org.hibernate.dialect.HSQLDialect;
|
||||
import org.hibernate.dialect.MariaDBDialect;
|
||||
import org.hibernate.dialect.MySQLDialect;
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.dialect.PostgreSQLDialect;
|
||||
import org.hibernate.dialect.SQLServerDialect;
|
||||
import org.hibernate.dialect.SybaseDialect;
|
||||
import org.hibernate.engine.jdbc.Size;
|
||||
import org.hibernate.mapping.BasicValue;
|
||||
import org.hibernate.mapping.Column;
|
||||
import org.hibernate.mapping.PersistentClass;
|
||||
import org.hibernate.mapping.Property;
|
||||
import org.hibernate.type.descriptor.DateTimeUtils;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.DomainModelScope;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.SkipForDialect;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@SuppressWarnings("JUnitMalformedDeclaration")
|
||||
public class FractionalSecondsTests {
|
||||
@Test
|
||||
@DomainModel(annotatedClasses = {TestEntity.class, TestEntity0.class, TestEntity3.class, TestEntity9.class} )
|
||||
void testMapping(DomainModelScope scope) {
|
||||
final MetadataImplementor domainModel = scope.getDomainModel();
|
||||
|
||||
final Dialect dialect = domainModel.getDatabase().getDialect();
|
||||
final int defaultPrecision = dialect.getDefaultTimestampPrecision();
|
||||
|
||||
final PersistentClass entityBinding = scope.getEntityBinding( TestEntity.class );
|
||||
checkPrecision( "theInstant", defaultPrecision, entityBinding, domainModel );
|
||||
checkPrecision( "theLocalDateTime", defaultPrecision, entityBinding, domainModel );
|
||||
checkPrecision( "theLocalTime", defaultPrecision, entityBinding, domainModel );
|
||||
checkPrecision( "theOffsetDateTime", defaultPrecision, entityBinding, domainModel );
|
||||
checkPrecision( "theOffsetTime", defaultPrecision, entityBinding, domainModel );
|
||||
checkPrecision( "theZonedDateTime", defaultPrecision, entityBinding, domainModel );
|
||||
|
||||
final PersistentClass entityBinding0 = scope.getEntityBinding( TestEntity0.class );
|
||||
checkPrecision( "theInstant", 0, entityBinding0, domainModel );
|
||||
|
||||
final PersistentClass entityBinding3 = scope.getEntityBinding( TestEntity3.class );
|
||||
checkPrecision( "theInstant", 3, entityBinding3, domainModel );
|
||||
checkPrecision( "theLocalDateTime", 3, entityBinding3, domainModel );
|
||||
checkPrecision( "theLocalTime", 3, entityBinding3, domainModel );
|
||||
|
||||
final PersistentClass entityBinding9 = scope.getEntityBinding( TestEntity9.class );
|
||||
checkPrecision( "theInstant", 9, entityBinding9, domainModel );
|
||||
checkPrecision( "theOffsetDateTime", 9, entityBinding9, domainModel );
|
||||
checkPrecision( "theOffsetTime", 9, entityBinding9, domainModel );
|
||||
checkPrecision( "theZonedDateTime", 9, entityBinding9, domainModel );
|
||||
}
|
||||
|
||||
private void checkPrecision(
|
||||
String propertyName,
|
||||
int expectedMinimumSize,
|
||||
PersistentClass entityBinding,
|
||||
MetadataImplementor domainModel) {
|
||||
final Property theInstant = entityBinding.getProperty( propertyName );
|
||||
final BasicValue value = (BasicValue) theInstant.getValue();
|
||||
final Column column = (Column) value.getColumn();
|
||||
final Size columnSize = column.getColumnSize( value.getDialect(), domainModel );
|
||||
assertThat( columnSize.getPrecision() ).isEqualTo( expectedMinimumSize );
|
||||
}
|
||||
|
||||
@Test
|
||||
@DomainModel(annotatedClasses = TestEntity.class)
|
||||
@SessionFactory
|
||||
@SkipForDialect( dialectClass = DB2Dialect.class, reason = "Occasional mismatch in rounding versus our code" )
|
||||
@SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true)
|
||||
void testUsage(SessionFactoryScope scope) {
|
||||
final Instant start = Instant.now();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TestEntity testEntity = new TestEntity();
|
||||
testEntity.id = 1;
|
||||
testEntity.theInstant = start;
|
||||
session.persist( testEntity );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TestEntity testEntity = session.find( TestEntity.class, 1 );
|
||||
|
||||
final Dialect dialect = session.getSessionFactory().getJdbcServices().getDialect();
|
||||
if ( dialect instanceof DerbyDialect
|
||||
|| dialect instanceof MariaDBDialect ) {
|
||||
assertThat( testEntity.theInstant ).isEqualTo( start );
|
||||
}
|
||||
else {
|
||||
assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 6 ) );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@DomainModel(annotatedClasses = TestEntity0.class)
|
||||
@SessionFactory
|
||||
@SkipForDialect( dialectClass = H2Dialect.class, reason = "Occasional mismatch in rounding versus our code" )
|
||||
@SkipForDialect( dialectClass = MariaDBDialect.class, reason = "Occasional mismatch in rounding versus our code" )
|
||||
@SkipForDialect( dialectClass = MySQLDialect.class, reason = "Occasional mismatch in rounding versus our code", matchSubTypes = true )
|
||||
@SkipForDialect( dialectClass = OracleDialect.class, reason = "Occasional mismatch in rounding versus our code" )
|
||||
@SkipForDialect( dialectClass = SQLServerDialect.class, reason = "Occasional mismatch in rounding versus our code" )
|
||||
@SkipForDialect( dialectClass = PostgreSQLDialect.class, reason = "Occasional mismatch in rounding versus our code", matchSubTypes = true )
|
||||
@SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp" )
|
||||
@SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true)
|
||||
void testUsage0(SessionFactoryScope scope) {
|
||||
final Instant start = Instant.now();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TestEntity0 testEntity = new TestEntity0();
|
||||
testEntity.id = 1;
|
||||
testEntity.theInstant = start;
|
||||
session.persist( testEntity );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TestEntity0 testEntity = session.find( TestEntity0.class, 1 );
|
||||
assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 0 ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@DomainModel(annotatedClasses = TestEntity3.class)
|
||||
@SessionFactory
|
||||
@SkipForDialect( dialectClass = MariaDBDialect.class, reason = "Occasional mismatch in rounding versus our code" )
|
||||
@SkipForDialect( dialectClass = HSQLDialect.class, reason = "Occasional mismatch in rounding versus our code" )
|
||||
@SkipForDialect( dialectClass = DB2Dialect.class, reason = "Occasional mismatch in rounding versus our code" )
|
||||
@SkipForDialect( dialectClass = DerbyDialect.class, reason = "Derby does not support sized timestamp" )
|
||||
@SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true)
|
||||
void testUsage3(SessionFactoryScope scope) {
|
||||
final Instant start = Instant.now();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TestEntity3 testEntity = new TestEntity3();
|
||||
testEntity.id = 1;
|
||||
testEntity.theInstant = start;
|
||||
session.persist( testEntity );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TestEntity3 testEntity = session.find( TestEntity3.class, 1 );
|
||||
assertThat( testEntity.theInstant ).isEqualTo( DateTimeUtils.roundToSecondPrecision( start, 3 ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@DomainModel(annotatedClasses = TestEntity9.class)
|
||||
@SessionFactory
|
||||
@SkipForDialect( dialectClass = MariaDBDialect.class, reason = "MariaDB only supports precision <= 6" )
|
||||
@SkipForDialect( dialectClass = MySQLDialect.class, reason = "MySQL only supports precision <= 6", matchSubTypes = true )
|
||||
@SkipForDialect( dialectClass = SQLServerDialect.class, reason = "SQL Server only supports precision <= 6" )
|
||||
@SkipForDialect(dialectClass = SybaseDialect.class, reason = "Because... Sybase...", matchSubTypes = true)
|
||||
void testUsage9(SessionFactoryScope scope) {
|
||||
final Instant start = Instant.now();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TestEntity9 testEntity = new TestEntity9();
|
||||
testEntity.id = 1;
|
||||
testEntity.theInstant = start;
|
||||
session.persist( testEntity );
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
final TestEntity9 testEntity = session.find( TestEntity9.class, 1 );
|
||||
|
||||
assertThat( testEntity.theInstant ).isEqualTo( start );
|
||||
} );
|
||||
}
|
||||
|
||||
@Entity(name="TestEntity")
|
||||
@Table(name="TestEntity")
|
||||
public static class TestEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
private Instant theInstant;
|
||||
|
||||
private LocalDateTime theLocalDateTime;
|
||||
|
||||
private LocalTime theLocalTime;
|
||||
|
||||
private OffsetDateTime theOffsetDateTime;
|
||||
|
||||
private OffsetTime theOffsetTime;
|
||||
|
||||
private ZonedDateTime theZonedDateTime;
|
||||
|
||||
}
|
||||
|
||||
@Entity(name="TestEntity0")
|
||||
@Table(name="TestEntity0")
|
||||
public static class TestEntity0 {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
@FractionalSeconds(0)
|
||||
private Instant theInstant;
|
||||
|
||||
}
|
||||
|
||||
@Entity(name="TestEntity3")
|
||||
@Table(name="TestEntity3")
|
||||
public static class TestEntity3 {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
@FractionalSeconds(3)
|
||||
private Instant theInstant;
|
||||
|
||||
@FractionalSeconds(3)
|
||||
private LocalDateTime theLocalDateTime;
|
||||
|
||||
@FractionalSeconds(3)
|
||||
private LocalTime theLocalTime;
|
||||
|
||||
}
|
||||
|
||||
@Entity(name="TestEntity9")
|
||||
@Table(name="TestEntity9")
|
||||
public static class TestEntity9 {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
@FractionalSeconds(9)
|
||||
private Instant theInstant;
|
||||
|
||||
@FractionalSeconds(9)
|
||||
private OffsetDateTime theOffsetDateTime;
|
||||
|
||||
@FractionalSeconds(9)
|
||||
private OffsetTime theOffsetTime;
|
||||
|
||||
@FractionalSeconds(9)
|
||||
private ZonedDateTime theZonedDateTime;
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue