Implement support for TimeZoneStorageType.COLUMN
This commit is contained in:
parent
fa750a9c26
commit
964e72f536
|
@ -273,9 +273,10 @@ Specifies whether to automatically quote any names that are deemed keywords.
|
|||
`*hibernate.timezone.default_storage*` (e.g. `COLUMN`, `NATIVE`, `AUTO` or `NORMALIZE` (default value))::
|
||||
Global setting for configuring the default storage for the time zone information for time zone based types.
|
||||
+
|
||||
`NORMALIZE`::: Does not store the time zone, and instead normalizes timestamps to UTC
|
||||
`NATIVE`::: Stores the time zone by using the `with time zone` type. Error if `Dialect#getTimeZoneSupport()` is not `NATIVE`
|
||||
`AUTO`::: Stores the time zone either with `NATIVE` if `Dialect#getTimeZoneSupport()` is `NATIVE`, otherwise uses the `COLUMN` strategy.
|
||||
`NORMALIZE`::: Does not store the time zone information, and instead normalizes timestamps to UTC
|
||||
`COLUMN`::: Stores the time zone information in a separate column; works in conjunction with `@TimeZoneColumn`
|
||||
`NATIVE`::: Stores the time zone information by using the `with time zone` type. Error if `Dialect#getTimeZoneSupport()` is not `NATIVE`
|
||||
`AUTO`::: Stores the time zone information either with `NATIVE` if `Dialect#getTimeZoneSupport()` is `NATIVE`, otherwise uses the `COLUMN` strategy.
|
||||
+
|
||||
The default value is given by the {@link org.hibernate.annotations.TimeZoneStorageType#NORMALIZE},
|
||||
meaning that time zone information is not stored by default, but timestamps are normalized instead.
|
||||
|
|
|
@ -1509,7 +1509,7 @@ The `JavaType` resolved earlier is then inspected for a number of special cases.
|
|||
still uses any explicit `JdbcType` indicators
|
||||
. For temporal values, we check for `@Temporal` and create an enumeration mapping. Note that this resolution
|
||||
still uses any explicit `JdbcType` indicators; this includes `@JdbcType` and `@JdbcTypeCode`, as well as
|
||||
`@TimeZoneStorage` if appropriate.
|
||||
`@TimeZoneStorage` and `@TimeZoneColumn` if appropriate.
|
||||
|
||||
The fallback at this point is to use the `JavaType` and `JdbcType` determined in earlier steps to create a
|
||||
JDBC-mapping (which encapsulates the `JavaType` and `JdbcType`) and combines it with the resolved `MutabilityPlan`
|
||||
|
@ -1668,12 +1668,14 @@ boil down to the 3 main Date/Time types defined by the SQL specification:
|
|||
DATE:: Represents a calendar date by storing years, months and days.
|
||||
TIME:: Represents the time of a day by storing hours, minutes and seconds.
|
||||
TIMESTAMP:: Represents both a DATE and a TIME plus nanoseconds.
|
||||
TIMESTAMP WITH TIME ZONE:: Represents both a DATE and a TIME plus nanoseconds and zone id or offset.
|
||||
|
||||
The mapping of `java.time` temporal types to the specific SQL Date/Time types is implied as follows:
|
||||
|
||||
DATE:: `java.time.LocalDate`
|
||||
TIME:: `java.time.LocalTime`, `java.time.OffsetTime`
|
||||
TIMESTAMP:: `java.time.Instant`, `java.time.LocalDateTime`, `java.time.OffsetDateTime` and `java.time.ZonedDateTime`
|
||||
TIMESTAMP WITH TIME ZONE:: `java.time.OffsetDateTime`, `java.time.ZonedDateTime`
|
||||
|
||||
Although Hibernate recommends the use of the `java.time` package for representing temporal values,
|
||||
it does support using `java.sql.Date`, `java.sql.Time`, `java.sql.Timestamp`, `java.util.Date` and
|
||||
|
@ -1759,6 +1761,50 @@ Session session = sessionFactory()
|
|||
With this configuration property in place, Hibernate is going to call the https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTimestamp-int-java.sql.Timestamp-java.util.Calendar-[`PreparedStatement.setTimestamp(int parameterIndex, java.sql.Timestamp, Calendar cal)`] or
|
||||
https://docs.oracle.com/javase/8/docs/api/java/sql/PreparedStatement.html#setTime-int-java.sql.Time-java.util.Calendar-[`PreparedStatement.setTime(int parameterIndex, java.sql.Time x, Calendar cal)`], where the `java.util.Calendar` references the time zone provided via the `hibernate.jdbc.time_zone` property.
|
||||
|
||||
[[basic-timestamp-with-time-zone]]
|
||||
===== Handling time zoned temporal data
|
||||
|
||||
By default, Hibernate will convert and normalize `OffsetDateTime` and `ZonedDateTime` to `java.sql.Timestamp` in UTC.
|
||||
This behavior can be altered by configuring the `hibernate.timezone.default_storage` property
|
||||
|
||||
[source,java]
|
||||
----
|
||||
settings.put(
|
||||
AvailableSettings.TIMEZONE_DEFAULT_STORAGE,
|
||||
TimeZoneStorageType.AUTO
|
||||
);
|
||||
----
|
||||
|
||||
Other possible storage types are `AUTO`, `COLUMN`, `NATIVE` and `NORMALIZE` (the default).
|
||||
With `COLUMN`, Hibernate will save the time zone information into a dedicated column,
|
||||
whereas `NATIVE` will require the support of database for a `TIMESTAMP WITH TIME ZONE` data type
|
||||
that retains the time zone information.
|
||||
`NORMALIZE` doesn't store time zone information and will simply convert the timestamp to UTC.
|
||||
Hibernate understands what a database/dialect supports through `Dialect#getTimeZoneSupport`
|
||||
and will abort with a boot error if the `NATIVE` is used in conjunction with a database that doesn't support this.
|
||||
For `AUTO`, Hibernate tries to use `NATIVE` if possible and falls back to `COLUMN` otherwise.
|
||||
|
||||
==== `@TimeZoneStorage`
|
||||
|
||||
Hibernate supports defining the storage to use for time zone information for individual properties
|
||||
via the `@TimeZoneStorage` and `@TimeZoneColumn` annotations.
|
||||
The storage type can be specified via the `@TimeZoneStorage` by specifying a `org.hibernate.annotations.TimeZoneStorageType`.
|
||||
The default storage type is `AUTO` which will ensure that the time zone information is retained.
|
||||
The `@TimeZoneColumn` annotation can be used in conjunction with `AUTO` or `COLUMN` and allows to define
|
||||
the column details for the time zone information storage.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Storing the zone offset might be problematic for future timestamps as zone rules can change.
|
||||
Due to this, storing the offset is only safe for past timestamps, and we advise sticking to the `NORMALIZE` strategy by default.
|
||||
|
||||
.`@TimeZoneColumn` usage
|
||||
====
|
||||
[source, JAVA, indent=0]
|
||||
----
|
||||
include::{sourcedir}/basic/TimeZoneStorageMappingTests.java[tags=time-zone-column-examples-mapping-example]
|
||||
----
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
package org.hibernate.userguide.mapping.basic;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* 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.userguide.mapping.basic;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.annotations.TimeZoneColumn;
|
||||
import org.hibernate.annotations.TimeZoneStorage;
|
||||
import org.hibernate.annotations.TimeZoneStorageType;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
|
||||
import org.hibernate.testing.orm.junit.DialectFeatureChecks;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.RequiresDialectFeature;
|
||||
import org.hibernate.testing.orm.junit.ServiceRegistry;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.hibernate.testing.orm.junit.Setting;
|
||||
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 jakarta.persistence.Table;
|
||||
import jakarta.persistence.Tuple;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
@DomainModel(annotatedClasses = TimeZoneStorageMappingTests.TimeZoneStorageEntity.class)
|
||||
@SessionFactory
|
||||
@ServiceRegistry(settings = @Setting( name = AvailableSettings.TIMEZONE_DEFAULT_STORAGE, value = "AUTO"))
|
||||
public class TimeZoneStorageMappingTests {
|
||||
|
||||
private static final OffsetDateTime OFFSET_DATE_TIME = OffsetDateTime.of(
|
||||
LocalDateTime.of(
|
||||
2022,
|
||||
3,
|
||||
1,
|
||||
12,
|
||||
0,
|
||||
0
|
||||
),
|
||||
ZoneOffset.ofHoursMinutes( 5, 45 )
|
||||
);
|
||||
private static final ZonedDateTime ZONED_DATE_TIME = ZonedDateTime.of(
|
||||
LocalDateTime.of(
|
||||
2022,
|
||||
3,
|
||||
1,
|
||||
12,
|
||||
0,
|
||||
0
|
||||
),
|
||||
ZoneOffset.ofHoursMinutes( 5, 45 )
|
||||
);
|
||||
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern( "dd/MM/yyyy 'at' HH:mm:ssxxx" );
|
||||
|
||||
@BeforeEach
|
||||
public void setup(SessionFactoryScope scope) {
|
||||
scope.inTransaction( s -> s.persist( new TimeZoneStorageEntity( 1, OFFSET_DATE_TIME, ZONED_DATE_TIME ) ) );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void destroy(SessionFactoryScope scope) {
|
||||
scope.inTransaction( s -> s.createMutationQuery( "delete from java.lang.Object" ).executeUpdate() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOffsetRetainedAuto(SessionFactoryScope scope) {
|
||||
testOffsetRetained( scope, "Auto" );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOffsetRetainedColumn(SessionFactoryScope scope) {
|
||||
testOffsetRetained( scope, "Column" );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class)
|
||||
public void testOffsetRetainedFormatAuto(SessionFactoryScope scope) {
|
||||
testOffsetRetainedFormat( scope, "Auto" );
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class)
|
||||
public void testOffsetRetainedFormatColumn(SessionFactoryScope scope) {
|
||||
testOffsetRetainedFormat( scope, "Column" );
|
||||
}
|
||||
|
||||
public void testOffsetRetained(SessionFactoryScope scope, String suffix) {
|
||||
scope.inSession(
|
||||
session -> {
|
||||
List<Tuple> resultList = session.createQuery(
|
||||
"select " +
|
||||
"e.offsetDateTime" + suffix + ", " +
|
||||
"e.zonedDateTime" + suffix + ", " +
|
||||
"extract(offset from e.offsetDateTime" + suffix + "), " +
|
||||
"extract(offset from e.zonedDateTime" + suffix + "), " +
|
||||
"e.offsetDateTime" + suffix + " + 1 hour, " +
|
||||
"e.zonedDateTime" + suffix + " + 1 hour, " +
|
||||
"e.offsetDateTime" + suffix + " + 1 hour - e.offsetDateTime" + suffix + ", " +
|
||||
"e.zonedDateTime" + suffix + " + 1 hour - e.zonedDateTime" + suffix + ", " +
|
||||
"1 from TimeZoneStorageEntity e " +
|
||||
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
|
||||
Tuple.class
|
||||
).getResultList();
|
||||
assertThat( resultList.get( 0 ).get( 0, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME ) );
|
||||
assertThat( resultList.get( 0 ).get( 1, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME ) );
|
||||
assertThat( resultList.get( 0 ).get( 2, ZoneOffset.class ), Matchers.is( OFFSET_DATE_TIME.getOffset() ) );
|
||||
assertThat( resultList.get( 0 ).get( 3, ZoneOffset.class ), Matchers.is( ZONED_DATE_TIME.getOffset() ) );
|
||||
assertThat( resultList.get( 0 ).get( 4, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME.plusHours( 1L ) ) );
|
||||
assertThat( resultList.get( 0 ).get( 5, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME.plusHours( 1L ) ) );
|
||||
assertThat( resultList.get( 0 ).get( 6, Duration.class ), Matchers.is( Duration.ofHours( 1L ) ) );
|
||||
assertThat( resultList.get( 0 ).get( 7, Duration.class ), Matchers.is( Duration.ofHours( 1L ) ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void testOffsetRetainedFormat(SessionFactoryScope scope, String suffix) {
|
||||
scope.inSession(
|
||||
session -> {
|
||||
List<Tuple> resultList = session.createQuery(
|
||||
"select " +
|
||||
"format(e.offsetDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " +
|
||||
"format(e.zonedDateTime" + suffix + " as 'dd/MM/yyyy ''at'' HH:mm:ssxxx'), " +
|
||||
"1 from TimeZoneStorageEntity e " +
|
||||
"where e.offsetDateTime" + suffix + " = e.offsetDateTime" + suffix,
|
||||
Tuple.class
|
||||
).getResultList();
|
||||
assertThat( resultList.get( 0 ).get( 0, String.class ), Matchers.is( FORMATTER.format( OFFSET_DATE_TIME ) ) );
|
||||
assertThat( resultList.get( 0 ).get( 1, String.class ), Matchers.is( FORMATTER.format( ZONED_DATE_TIME ) ) );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// @Test
|
||||
// @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class)
|
||||
// public void testNormalize(SessionFactoryScope scope) {
|
||||
// scope.inSession(
|
||||
// session -> {
|
||||
// List<Tuple> resultList = session.createQuery(
|
||||
// "select e.offsetDateTimeNormalized, extract(offset from e.offsetDateTimeNormalized), e.zonedDateTimeNormalized, extract(offset from e.zonedDateTimeNormalized) from TimeZoneStorageEntity e",
|
||||
// Tuple.class
|
||||
// ).getResultList();
|
||||
// assertThat( resultList.get( 0 ).get( 0, OffsetDateTime.class ), Matchers.is( OFFSET_DATE_TIME ) );
|
||||
// assertThat( resultList.get( 0 ).get( 1, ZoneOffset.class ), Matchers.is( OFFSET_DATE_TIME.getOffset() ) );
|
||||
// assertThat( resultList.get( 0 ).get( 2, ZonedDateTime.class ), Matchers.is( ZONED_DATE_TIME ) );
|
||||
// assertThat( resultList.get( 0 ).get( 3, ZoneOffset.class ), Matchers.is( ZONED_DATE_TIME.getOffset() ) );
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
|
||||
@Entity(name = "TimeZoneStorageEntity")
|
||||
@Table(name = "TimeZoneStorageEntity")
|
||||
public static class TimeZoneStorageEntity {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
//end::time-zone-column-examples-mapping-example[]
|
||||
@TimeZoneStorage(TimeZoneStorageType.COLUMN)
|
||||
@TimeZoneColumn(name = "birthday_offset_offset")
|
||||
@Column(name = "birthday_offset")
|
||||
private OffsetDateTime offsetDateTimeColumn;
|
||||
|
||||
@TimeZoneStorage(TimeZoneStorageType.COLUMN)
|
||||
@TimeZoneColumn(name = "birthday_zoned_offset")
|
||||
@Column(name = "birthday_zoned")
|
||||
private ZonedDateTime zonedDateTimeColumn;
|
||||
//end::time-zone-column-examples-mapping-example[]
|
||||
|
||||
@TimeZoneStorage
|
||||
@Column(name = "birthday_offset_auto")
|
||||
private OffsetDateTime offsetDateTimeAuto;
|
||||
|
||||
@TimeZoneStorage
|
||||
@Column(name = "birthday_zoned_auto")
|
||||
private ZonedDateTime zonedDateTimeAuto;
|
||||
|
||||
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
|
||||
@Column(name = "birthday_offset_normalized")
|
||||
private OffsetDateTime offsetDateTimeNormalized;
|
||||
|
||||
@TimeZoneStorage(TimeZoneStorageType.NORMALIZE)
|
||||
@Column(name = "birthday_zoned_normalized")
|
||||
private ZonedDateTime zonedDateTimeNormalized;
|
||||
|
||||
public TimeZoneStorageEntity() {
|
||||
}
|
||||
|
||||
public TimeZoneStorageEntity(Integer id, OffsetDateTime offsetDateTime, ZonedDateTime zonedDateTime) {
|
||||
this.id = id;
|
||||
this.offsetDateTimeColumn = offsetDateTime;
|
||||
this.zonedDateTimeColumn = zonedDateTime;
|
||||
this.offsetDateTimeAuto = offsetDateTime;
|
||||
this.zonedDateTimeAuto = zonedDateTime;
|
||||
this.offsetDateTimeNormalized = offsetDateTime;
|
||||
this.zonedDateTimeNormalized = zonedDateTime;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,6 +19,10 @@ public enum TimeZoneStorageStrategy {
|
|||
* Stores the time zone through the "with time zone" types which retain the information.
|
||||
*/
|
||||
NATIVE,
|
||||
/**
|
||||
* Stores the time zone in a separate column.
|
||||
*/
|
||||
COLUMN,
|
||||
/**
|
||||
* Doesn't store the time zone, but instead normalizes to UTC.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
|
||||
/**
|
||||
* Specifies the mapped column for storing the time zone information.
|
||||
* The annotation can be used in conjunction with the <code>TimeZoneStorageType.AUTO</code> and
|
||||
* <code>TimeZoneStorageType.COLUMN</code>. The column is simply ignored if <code>TimeZoneStorageType.AUTO</code>
|
||||
* is used and the database supports native time zone storage.
|
||||
*
|
||||
* @author Christian Beikov
|
||||
* @author Steve Ebersole
|
||||
* @author Andrea Boriero
|
||||
* @see TimeZoneStorage
|
||||
* @see TimeZoneStorageType#COLUMN
|
||||
* @see TimeZoneStorageType#AUTO
|
||||
*/
|
||||
@Incubating
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ FIELD, METHOD })
|
||||
public @interface TimeZoneColumn {
|
||||
|
||||
/**
|
||||
* (Optional) The name of the column. Defaults to
|
||||
* the property or field name, suffixed by <code>_tz</code>.
|
||||
*/
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* (Optional) Whether the column is included in SQL INSERT
|
||||
* statements generated by the persistence provider.
|
||||
*/
|
||||
boolean insertable() default true;
|
||||
|
||||
/**
|
||||
* (Optional) Whether the column is included in SQL UPDATE
|
||||
* statements generated by the persistence provider.
|
||||
*/
|
||||
boolean updatable() default true;
|
||||
|
||||
/**
|
||||
* (Optional) The SQL fragment that is used when
|
||||
* generating the DDL for the column.
|
||||
* <p> Defaults to the generated SQL to create a
|
||||
* column of the inferred type.
|
||||
*/
|
||||
String columnDefinition() default "";
|
||||
|
||||
/**
|
||||
* (Optional) The name of the table that contains the column.
|
||||
* If absent the column is assumed to be in the primary table.
|
||||
*/
|
||||
String table() default "";
|
||||
|
||||
}
|
|
@ -38,6 +38,7 @@ import static java.lang.annotation.ElementType.METHOD;
|
|||
* @author Christian Beikov
|
||||
* @author Steve Ebersole
|
||||
* @author Andrea Boriero
|
||||
* @see TimeZoneColumn
|
||||
*/
|
||||
@Incubating
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
@ -46,5 +47,5 @@ public @interface TimeZoneStorage {
|
|||
/**
|
||||
* The storage strategy for the time zone information.
|
||||
*/
|
||||
TimeZoneStorageType value();
|
||||
TimeZoneStorageType value() default TimeZoneStorageType.AUTO;
|
||||
}
|
||||
|
|
|
@ -30,5 +30,18 @@ public enum TimeZoneStorageType {
|
|||
* Does not store the time zone, and instead normalizes
|
||||
* timestamps to UTC.
|
||||
*/
|
||||
NORMALIZE
|
||||
NORMALIZE,
|
||||
/**
|
||||
* Stores the time zone in a separate column; works in
|
||||
* conjunction with {@link TimeZoneColumn}.
|
||||
*/
|
||||
COLUMN,
|
||||
/**
|
||||
* Stores the time zone either with {@link #NATIVE} if
|
||||
* {@link Dialect#getTimeZoneSupport()} is
|
||||
* {@link org.hibernate.dialect.TimeZoneSupport#NATIVE},
|
||||
* otherwise uses the {@link #COLUMN} strategy.
|
||||
*/
|
||||
AUTO
|
||||
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* 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 org.hibernate.Incubating;
|
||||
|
||||
/**
|
||||
* The type of storage to use for the time zone information.
|
||||
*
|
||||
* @author Christian Beikov
|
||||
* @author Steve Ebersole
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
@Incubating
|
||||
public enum TimeZoneType {
|
||||
|
||||
/**
|
||||
* Stores the time zone id as String.
|
||||
*/
|
||||
ZONE_ID,
|
||||
/**
|
||||
* Stores the offset seconds of a timestamp as Integer.
|
||||
*/
|
||||
OFFSET;
|
||||
|
||||
}
|
|
@ -875,7 +875,7 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
|
|||
ConfigurationService configService) {
|
||||
final TimeZoneStorageType configuredTimeZoneStorageType = configService.getSetting(
|
||||
AvailableSettings.TIMEZONE_DEFAULT_STORAGE,
|
||||
TimeZoneStorageType.class,
|
||||
value -> TimeZoneStorageType.valueOf( value.toString() ),
|
||||
null
|
||||
);
|
||||
final TimeZoneStorageStrategy resolvedTimezoneStorage;
|
||||
|
@ -894,9 +894,25 @@ public class MetadataBuilderImpl implements MetadataBuilderImplementor, TypeCont
|
|||
}
|
||||
resolvedTimezoneStorage = TimeZoneStorageStrategy.NATIVE;
|
||||
break;
|
||||
case COLUMN:
|
||||
resolvedTimezoneStorage = TimeZoneStorageStrategy.COLUMN;
|
||||
break;
|
||||
case NORMALIZE:
|
||||
resolvedTimezoneStorage = TimeZoneStorageStrategy.NORMALIZE;
|
||||
break;
|
||||
case AUTO:
|
||||
switch ( timeZoneSupport ) {
|
||||
case NATIVE:
|
||||
resolvedTimezoneStorage = TimeZoneStorageStrategy.NATIVE;
|
||||
break;
|
||||
case NORMALIZE:
|
||||
case NONE:
|
||||
resolvedTimezoneStorage = TimeZoneStorageStrategy.COLUMN;
|
||||
break;
|
||||
default:
|
||||
throw new HibernateException( "Unsupported time zone support: " + timeZoneSupport );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new HibernateException( "Unsupported time zone storage type: " + configuredTimeZoneStorageType );
|
||||
}
|
||||
|
|
|
@ -65,6 +65,8 @@ public class VersionResolution<E> implements BasicValue.Resolution<E> {
|
|||
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
|
||||
if ( timeZoneStorageType != null ) {
|
||||
switch ( timeZoneStorageType ) {
|
||||
case COLUMN:
|
||||
return TimeZoneStorageStrategy.COLUMN;
|
||||
case NATIVE:
|
||||
return TimeZoneStorageStrategy.NATIVE;
|
||||
case NORMALIZE:
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.cfg;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
@ -25,16 +26,23 @@ import jakarta.persistence.MappedSuperclass;
|
|||
|
||||
import org.hibernate.AnnotationException;
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.TimeZoneStorageStrategy;
|
||||
import org.hibernate.annotations.ColumnTransformer;
|
||||
import org.hibernate.annotations.ColumnTransformers;
|
||||
import org.hibernate.annotations.TimeZoneColumn;
|
||||
import org.hibernate.annotations.TimeZoneStorage;
|
||||
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
|
||||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
import org.hibernate.annotations.common.reflection.XProperty;
|
||||
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
|
||||
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
|
||||
import org.hibernate.boot.model.naming.Identifier;
|
||||
import org.hibernate.boot.model.naming.ImplicitBasicColumnNameSource;
|
||||
import org.hibernate.boot.model.source.spi.AttributePath;
|
||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
|
@ -180,7 +188,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|
|||
this.currentPropertyForeignKeyOverride = null;
|
||||
}
|
||||
else {
|
||||
this.currentPropertyColumnOverride = buildColumnOverride( property, getPath() );
|
||||
this.currentPropertyColumnOverride = buildColumnOverride( property, getPath(), context );
|
||||
if ( this.currentPropertyColumnOverride.size() == 0 ) {
|
||||
this.currentPropertyColumnOverride = null;
|
||||
}
|
||||
|
@ -408,7 +416,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|
|||
if ( current.isAnnotationPresent( Entity.class ) || current.isAnnotationPresent( MappedSuperclass.class )
|
||||
|| current.isAnnotationPresent( Embeddable.class ) ) {
|
||||
//FIXME is embeddable override?
|
||||
Map<String, Column[]> currentOverride = buildColumnOverride( current, getPath() );
|
||||
Map<String, Column[]> currentOverride = buildColumnOverride( current, getPath(), context );
|
||||
Map<String, ColumnTransformer> currentTransformerOverride = buildColumnTransformerOverride( current, getPath() );
|
||||
Map<String, JoinColumn[]> currentJoinOverride = buildJoinColumnOverride( current, getPath() );
|
||||
Map<String, JoinTable> currentJoinTableOverride = buildJoinTableOverride( current, getPath() );
|
||||
|
@ -434,7 +442,10 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|
|||
holderForeignKeyOverride = foreignKeyOverride.size() > 0 ? foreignKeyOverride : null;
|
||||
}
|
||||
|
||||
private static Map<String, Column[]> buildColumnOverride(XAnnotatedElement element, String path) {
|
||||
private static Map<String, Column[]> buildColumnOverride(
|
||||
XAnnotatedElement element,
|
||||
String path,
|
||||
MetadataBuildingContext context) {
|
||||
Map<String, Column[]> columnOverride = new HashMap<>();
|
||||
if ( element != null ) {
|
||||
AttributeOverride singleOverride = element.getAnnotation( AttributeOverride.class );
|
||||
|
@ -474,6 +485,93 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|
|||
);
|
||||
}
|
||||
}
|
||||
else {
|
||||
final TimeZoneStorage timeZoneStorage = element.getAnnotation( TimeZoneStorage.class );
|
||||
if ( timeZoneStorage != null ) {
|
||||
switch ( timeZoneStorage.value() ) {
|
||||
case AUTO:
|
||||
if ( context.getBuildingOptions().getDefaultTimeZoneStorage() != TimeZoneStorageStrategy.COLUMN ) {
|
||||
break;
|
||||
}
|
||||
case COLUMN:
|
||||
final Column column;
|
||||
final Column annotatedColumn = element.getAnnotation( Column.class );
|
||||
if ( annotatedColumn != null ) {
|
||||
column = annotatedColumn;
|
||||
}
|
||||
else {
|
||||
// Base the name of the synthetic dateTime field on the name of the original attribute
|
||||
final Identifier implicitName = context.getObjectNameNormalizer().normalizeIdentifierQuoting(
|
||||
context.getBuildingOptions().getImplicitNamingStrategy().determineBasicColumnName(
|
||||
new ImplicitBasicColumnNameSource() {
|
||||
final AttributePath attributePath = AttributePath.parse( path );
|
||||
|
||||
@Override
|
||||
public AttributePath getAttributePath() {
|
||||
return attributePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCollectionElement() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetadataBuildingContext getBuildingContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
column = new ColumnImpl(
|
||||
implicitName.getText(),
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
"",
|
||||
"",
|
||||
0
|
||||
);
|
||||
}
|
||||
columnOverride.put(
|
||||
path + "." + AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME,
|
||||
new Column[] { column }
|
||||
);
|
||||
final Column offsetColumn;
|
||||
final TimeZoneColumn timeZoneColumn = element.getAnnotation( TimeZoneColumn.class );
|
||||
if ( timeZoneColumn != null ) {
|
||||
offsetColumn = new ColumnImpl(
|
||||
timeZoneColumn.name(),
|
||||
false,
|
||||
column.nullable(),
|
||||
timeZoneColumn.insertable(),
|
||||
timeZoneColumn.updatable(),
|
||||
timeZoneColumn.columnDefinition(),
|
||||
timeZoneColumn.table(),
|
||||
0
|
||||
);
|
||||
}
|
||||
else {
|
||||
offsetColumn = new ColumnImpl(
|
||||
column.name() + "_tz",
|
||||
false,
|
||||
column.nullable(),
|
||||
column.insertable(),
|
||||
column.updatable(),
|
||||
"",
|
||||
column.table(),
|
||||
0
|
||||
);
|
||||
}
|
||||
columnOverride.put(
|
||||
path + "." + AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME,
|
||||
new Column[] { offsetColumn }
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return columnOverride;
|
||||
}
|
||||
|
@ -574,4 +672,90 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
|
|||
public void setParentProperty(String parentProperty) {
|
||||
throw new AssertionFailure( "Setting the parent property to a non component" );
|
||||
}
|
||||
|
||||
private static class ColumnImpl implements Column {
|
||||
|
||||
private final String name;
|
||||
private final boolean unique;
|
||||
private final boolean nullable;
|
||||
private final boolean insertable;
|
||||
private final boolean updatable;
|
||||
private final String columnDefinition;
|
||||
private final String table;
|
||||
private final int precision;
|
||||
|
||||
private ColumnImpl(
|
||||
String name,
|
||||
boolean unique,
|
||||
boolean nullable,
|
||||
boolean insertable,
|
||||
boolean updatable,
|
||||
String columnDefinition,
|
||||
String table,
|
||||
int precision) {
|
||||
this.name = name;
|
||||
this.unique = unique;
|
||||
this.nullable = nullable;
|
||||
this.insertable = insertable;
|
||||
this.updatable = updatable;
|
||||
this.columnDefinition = columnDefinition;
|
||||
this.table = table;
|
||||
this.precision = precision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unique() {
|
||||
return unique;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nullable() {
|
||||
return nullable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean insertable() {
|
||||
return insertable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updatable() {
|
||||
return updatable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String columnDefinition() {
|
||||
return columnDefinition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String table() {
|
||||
return table;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return 255;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int precision() {
|
||||
return precision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int scale() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return Column.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.lang.reflect.InvocationTargetException;
|
|||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -30,6 +31,7 @@ import org.hibernate.AssertionFailure;
|
|||
import org.hibernate.FetchMode;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.TimeZoneStorageStrategy;
|
||||
import org.hibernate.annotations.BatchSize;
|
||||
import org.hibernate.annotations.Cache;
|
||||
import org.hibernate.annotations.Cascade;
|
||||
|
@ -89,6 +91,7 @@ import org.hibernate.annotations.Proxy;
|
|||
import org.hibernate.annotations.SortComparator;
|
||||
import org.hibernate.annotations.SortNatural;
|
||||
import org.hibernate.annotations.Source;
|
||||
import org.hibernate.annotations.TimeZoneStorage;
|
||||
import org.hibernate.annotations.Where;
|
||||
import org.hibernate.annotations.common.reflection.ReflectionManager;
|
||||
import org.hibernate.annotations.common.reflection.XAnnotatedElement;
|
||||
|
@ -156,6 +159,8 @@ import org.hibernate.type.descriptor.jdbc.JdbcType;
|
|||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
import org.hibernate.usertype.CompositeUserType;
|
||||
import org.hibernate.usertype.UserType;
|
||||
import org.hibernate.usertype.internal.OffsetDateTimeCompositeUserType;
|
||||
import org.hibernate.usertype.internal.ZonedDateTimeCompositeUserType;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.AttributeOverride;
|
||||
|
@ -2896,6 +2901,14 @@ public final class AnnotationBinder {
|
|||
if ( compositeType != null ) {
|
||||
return compositeType.value();
|
||||
}
|
||||
Class<? extends CompositeUserType<?>> compositeUserType = resolveTimeZoneStorageCompositeUserType(
|
||||
property,
|
||||
returnedClass,
|
||||
context
|
||||
);
|
||||
if ( compositeUserType != null ) {
|
||||
return compositeUserType;
|
||||
}
|
||||
}
|
||||
|
||||
if ( returnedClass != null ) {
|
||||
|
@ -2910,6 +2923,31 @@ public final class AnnotationBinder {
|
|||
return null;
|
||||
}
|
||||
|
||||
protected static Class<? extends CompositeUserType<?>> resolveTimeZoneStorageCompositeUserType(
|
||||
XProperty property,
|
||||
XClass returnedClass,
|
||||
MetadataBuildingContext context) {
|
||||
if ( property != null ) {
|
||||
final TimeZoneStorage timeZoneStorage = property.getAnnotation( TimeZoneStorage.class );
|
||||
if ( timeZoneStorage != null ) {
|
||||
switch ( timeZoneStorage.value() ) {
|
||||
case AUTO:
|
||||
if ( context.getBuildingOptions().getDefaultTimeZoneStorage() != TimeZoneStorageStrategy.COLUMN ) {
|
||||
return null;
|
||||
}
|
||||
case COLUMN:
|
||||
switch ( returnedClass.getName() ) {
|
||||
case "java.time.OffsetDateTime":
|
||||
return OffsetDateTimeCompositeUserType.class;
|
||||
case "java.time.ZonedDateTime":
|
||||
return ZonedDateTimeCompositeUserType.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isGlobalGeneratorNameGlobal(MetadataBuildingContext context) {
|
||||
return context.getBootstrapContext().getJpaCompliance().isGlobalGeneratorScopeEnabled();
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.hibernate.annotations.Mutability;
|
|||
import org.hibernate.annotations.Nationalized;
|
||||
import org.hibernate.annotations.Parameter;
|
||||
import org.hibernate.annotations.Target;
|
||||
import org.hibernate.annotations.TimeZoneColumn;
|
||||
import org.hibernate.annotations.TimeZoneStorage;
|
||||
import org.hibernate.annotations.TimeZoneStorageType;
|
||||
import org.hibernate.annotations.common.reflection.XClass;
|
||||
|
@ -178,6 +179,8 @@ public class BasicValueBinder implements JdbcTypeIndicators {
|
|||
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
|
||||
if ( timeZoneStorageType != null ) {
|
||||
switch ( timeZoneStorageType ) {
|
||||
case COLUMN:
|
||||
return TimeZoneStorageStrategy.COLUMN;
|
||||
case NATIVE:
|
||||
return TimeZoneStorageStrategy.NATIVE;
|
||||
case NORMALIZE:
|
||||
|
@ -656,7 +659,22 @@ public class BasicValueBinder implements JdbcTypeIndicators {
|
|||
}
|
||||
|
||||
final TimeZoneStorage timeZoneStorageAnn = attributeXProperty.getAnnotation( TimeZoneStorage.class );
|
||||
timeZoneStorageType = timeZoneStorageAnn != null ? timeZoneStorageAnn.value() : null;
|
||||
if ( timeZoneStorageAnn != null ) {
|
||||
timeZoneStorageType = timeZoneStorageAnn.value();
|
||||
final TimeZoneColumn timeZoneColumnAnn = attributeXProperty.getAnnotation( TimeZoneColumn.class );
|
||||
if ( timeZoneColumnAnn != null ) {
|
||||
if ( timeZoneStorageType != TimeZoneStorageType.AUTO && timeZoneStorageType != TimeZoneStorageType.COLUMN ) {
|
||||
throw new IllegalStateException(
|
||||
"@TimeZoneColumn can not be used in conjunction with @TimeZoneStorage( " + timeZoneStorageType +
|
||||
" ) with attribute " + attributeXProperty.getDeclaringClass().getName() +
|
||||
'.' + attributeXProperty.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
timeZoneStorageType = null;
|
||||
}
|
||||
|
||||
normalSupplementalDetails( attributeXProperty);
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.hibernate.LockMode;
|
|||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.boot.model.TypeContributions;
|
||||
import org.hibernate.dialect.function.CommonFunctionFactory;
|
||||
import org.hibernate.dialect.function.FormatFunction;
|
||||
import org.hibernate.dialect.pagination.LimitHandler;
|
||||
import org.hibernate.dialect.pagination.OffsetFetchLimitHandler;
|
||||
import org.hibernate.dialect.sequence.PostgreSQLSequenceSupport;
|
||||
|
@ -230,11 +231,10 @@ public class CockroachDialect extends Dialect {
|
|||
functionFactory.pi();
|
||||
functionFactory.trunc(); //TODO: emulate second arg
|
||||
|
||||
queryEngine.getSqmFunctionRegistry().namedDescriptorBuilder("format", "experimental_strftime")
|
||||
.setInvariantType( stringType )
|
||||
.setArgumentsValidator( CommonFunctionFactory.formatValidator() )
|
||||
.setArgumentListSignature("(TEMPORAL datetime as STRING pattern)")
|
||||
.register();
|
||||
queryEngine.getSqmFunctionRegistry().register(
|
||||
"format",
|
||||
new FormatFunction( "experimental_strftime", queryEngine.getTypeConfiguration() )
|
||||
);
|
||||
functionFactory.windowFunctions();
|
||||
functionFactory.listagg_stringAgg( "string" );
|
||||
functionFactory.inverseDistributionOrderedSetAggregates();
|
||||
|
|
|
@ -287,8 +287,10 @@ public class DB2Dialect extends Dialect {
|
|||
)
|
||||
);
|
||||
|
||||
queryEngine.getSqmFunctionRegistry().register( "format",
|
||||
new DB2FormatEmulation( queryEngine.getTypeConfiguration() ) );
|
||||
queryEngine.getSqmFunctionRegistry().register(
|
||||
"format",
|
||||
new DB2FormatEmulation( queryEngine.getTypeConfiguration() )
|
||||
);
|
||||
|
||||
queryEngine.getSqmFunctionRegistry().namedDescriptorBuilder( "posstr" )
|
||||
.setInvariantType(
|
||||
|
|
|
@ -63,13 +63,14 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
|
||||
@Override
|
||||
protected void visitAnsiCaseSearchedExpression(
|
||||
CaseSearchedExpression caseSearchedExpression,
|
||||
CaseSearchedExpression expression,
|
||||
Consumer<Expression> resultRenderer) {
|
||||
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSearchedExpression ) ) {
|
||||
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
|
||||
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|
||||
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
|
||||
final List<CaseSearchedExpression.WhenFragment> whenFragments = expression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
super.visitAnsiCaseSearchedExpression(
|
||||
caseSearchedExpression,
|
||||
expression,
|
||||
e -> {
|
||||
if ( e == firstResult ) {
|
||||
renderCasted( e );
|
||||
|
@ -81,19 +82,20 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
);
|
||||
}
|
||||
else {
|
||||
super.visitAnsiCaseSearchedExpression( caseSearchedExpression, resultRenderer );
|
||||
super.visitAnsiCaseSearchedExpression( expression, resultRenderer );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void visitAnsiCaseSimpleExpression(
|
||||
CaseSimpleExpression caseSimpleExpression,
|
||||
CaseSimpleExpression expression,
|
||||
Consumer<Expression> resultRenderer) {
|
||||
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( caseSimpleExpression ) ) {
|
||||
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
|
||||
if ( getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT && areAllResultsParameters( expression )
|
||||
|| areAllResultsPlainParametersOrLiterals( expression ) ) {
|
||||
final List<CaseSimpleExpression.WhenFragment> whenFragments = expression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
super.visitAnsiCaseSimpleExpression(
|
||||
caseSimpleExpression,
|
||||
expression,
|
||||
e -> {
|
||||
if ( e == firstResult ) {
|
||||
renderCasted( e );
|
||||
|
@ -105,10 +107,52 @@ public class HSQLSqlAstTranslator<T extends JdbcOperation> extends AbstractSqlAs
|
|||
);
|
||||
}
|
||||
else {
|
||||
super.visitAnsiCaseSimpleExpression( caseSimpleExpression, resultRenderer );
|
||||
super.visitAnsiCaseSimpleExpression( expression, resultRenderer );
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean areAllResultsPlainParametersOrLiterals(CaseSearchedExpression caseSearchedExpression) {
|
||||
final List<CaseSearchedExpression.WhenFragment> whenFragments = caseSearchedExpression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|
||||
|| isLiteral( firstResult ) ) {
|
||||
for ( int i = 1; i < whenFragments.size(); i++ ) {
|
||||
final Expression result = whenFragments.get( i ).getResult();
|
||||
if ( isParameter( result ) ) {
|
||||
if ( getParameterRenderingMode() != SqlAstNodeRenderingMode.DEFAULT ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ( !isLiteral( result ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean areAllResultsPlainParametersOrLiterals(CaseSimpleExpression caseSimpleExpression) {
|
||||
final List<CaseSimpleExpression.WhenFragment> whenFragments = caseSimpleExpression.getWhenFragments();
|
||||
final Expression firstResult = whenFragments.get( 0 ).getResult();
|
||||
if ( isParameter( firstResult ) && getParameterRenderingMode() == SqlAstNodeRenderingMode.DEFAULT
|
||||
|| isLiteral( firstResult ) ) {
|
||||
for ( int i = 1; i < whenFragments.size(); i++ ) {
|
||||
final Expression result = whenFragments.get( i ).getResult();
|
||||
if ( isParameter( result ) ) {
|
||||
if ( getParameterRenderingMode() != SqlAstNodeRenderingMode.DEFAULT ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ( !isLiteral( result ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFilterClause() {
|
||||
return true;
|
||||
|
|
|
@ -1014,6 +1014,12 @@ public class MySQLDialect extends Dialect {
|
|||
return getMySQLVersion().isBefore( 5, 5 ) ? MyISAMStorageEngine.INSTANCE : InnoDBStorageEngine.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimeZoneSupport getTimeZoneSupport() {
|
||||
// In MySQL and MariaDB, the TIMESTAMP type normalize to UTC just like PostgreSQL
|
||||
return TimeZoneSupport.NORMALIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendLiteral(SqlAppender appender, String literal) {
|
||||
appender.appendSql( '\'' );
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.dialect;
|
|||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
|
@ -15,6 +16,7 @@ import java.sql.SQLException;
|
|||
import java.sql.Types;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.internal.util.ReflectHelper;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import org.hibernate.type.descriptor.ValueBinder;
|
||||
|
@ -34,12 +36,27 @@ import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
|
|||
public class PostgreSQLIntervalSecondJdbcType implements AdjustableJdbcType {
|
||||
|
||||
public static final PostgreSQLIntervalSecondJdbcType INSTANCE = new PostgreSQLIntervalSecondJdbcType();
|
||||
private static final Class<?> PG_INTERVAL_CLASS;
|
||||
private static final Constructor<Object> PG_INTERVAL_CONSTRUCTOR;
|
||||
private static final Method PG_INTERVAL_GET_DAYS;
|
||||
private static final Method PG_INTERVAL_GET_HOURS;
|
||||
private static final Method PG_INTERVAL_GET_MINUTES;
|
||||
private static final Method PG_INTERVAL_GET_SECONDS;
|
||||
private static final Method PG_INTERVAL_GET_MICRO_SECONDS;
|
||||
private static final long SECONDS_PER_DAY = 86400;
|
||||
private static final long SECONDS_PER_HOUR = 3600;
|
||||
private static final long SECONDS_PER_MINUTE = 60;
|
||||
|
||||
static {
|
||||
Constructor<Object> constructor;
|
||||
Class<?> pgIntervalClass;
|
||||
Method pgIntervalGetDays;
|
||||
Method pgIntervalGetHours;
|
||||
Method pgIntervalGetMinutes;
|
||||
Method pgIntervalGetSeconds;
|
||||
Method pgIntervalGetMicroSeconds;
|
||||
try {
|
||||
final Class<?> pgIntervalClass = ReflectHelper.classForName(
|
||||
pgIntervalClass = ReflectHelper.classForName(
|
||||
"org.postgresql.util.PGInterval",
|
||||
PostgreSQLIntervalSecondJdbcType.class
|
||||
);
|
||||
|
@ -51,11 +68,22 @@ public class PostgreSQLIntervalSecondJdbcType implements AdjustableJdbcType {
|
|||
int.class,
|
||||
double.class
|
||||
);
|
||||
pgIntervalGetDays = pgIntervalClass.getDeclaredMethod( "getDays" );
|
||||
pgIntervalGetHours = pgIntervalClass.getDeclaredMethod( "getHours" );
|
||||
pgIntervalGetMinutes = pgIntervalClass.getDeclaredMethod( "getMinutes" );
|
||||
pgIntervalGetSeconds = pgIntervalClass.getDeclaredMethod( "getWholeSeconds" );
|
||||
pgIntervalGetMicroSeconds = pgIntervalClass.getDeclaredMethod( "getMicroSeconds" );
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException( "Could not initialize PostgreSQLPGObjectJdbcType", e );
|
||||
}
|
||||
PG_INTERVAL_CLASS = pgIntervalClass;
|
||||
PG_INTERVAL_CONSTRUCTOR = constructor;
|
||||
PG_INTERVAL_GET_DAYS = pgIntervalGetDays;
|
||||
PG_INTERVAL_GET_HOURS = pgIntervalGetHours;
|
||||
PG_INTERVAL_GET_MINUTES = pgIntervalGetMinutes;
|
||||
PG_INTERVAL_GET_SECONDS = pgIntervalGetSeconds;
|
||||
PG_INTERVAL_GET_MICRO_SECONDS = pgIntervalGetMicroSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -139,18 +167,36 @@ public class PostgreSQLIntervalSecondJdbcType implements AdjustableJdbcType {
|
|||
return new BasicExtractor<>( javaType, this ) {
|
||||
@Override
|
||||
protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException {
|
||||
return getJavaType().wrap( rs.getString( paramIndex ), options );
|
||||
return getJavaType().wrap( getValue( rs.getObject( paramIndex ) ), options );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
|
||||
return getJavaType().wrap( statement.getString( index ), options );
|
||||
return getJavaType().wrap( getValue( statement.getObject( index ) ), options );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected X doExtract(CallableStatement statement, String name, WrapperOptions options)
|
||||
throws SQLException {
|
||||
return getJavaType().wrap( statement.getString( name ), options );
|
||||
return getJavaType().wrap( getValue( statement.getObject( name ) ), options );
|
||||
}
|
||||
|
||||
private Object getValue(Object value) {
|
||||
if ( PG_INTERVAL_CLASS.isInstance( value ) ) {
|
||||
try {
|
||||
final long seconds = (int) PG_INTERVAL_GET_SECONDS.invoke( value )
|
||||
+ SECONDS_PER_DAY * (int) PG_INTERVAL_GET_DAYS.invoke( value )
|
||||
+ SECONDS_PER_HOUR * (int) PG_INTERVAL_GET_HOURS.invoke( value )
|
||||
+ SECONDS_PER_MINUTE * (int) PG_INTERVAL_GET_MINUTES.invoke( value );
|
||||
final long nanos = 1000L * (int) PG_INTERVAL_GET_MICRO_SECONDS.invoke( value );
|
||||
|
||||
return Duration.ofSeconds( seconds, nanos );
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new HibernateException( "Couldn't create Duration from interval", e );
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ package org.hibernate.dialect;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
|
||||
/**
|
||||
* @author Gavin King
|
||||
*/
|
||||
|
@ -47,7 +49,7 @@ public class Replacer {
|
|||
|
||||
public Replacer(String format, String quote, String delimiter) {
|
||||
this.delimiter = delimiter;
|
||||
this.chunks = format.split( quote );
|
||||
this.chunks = StringHelper.splitFull( quote, format );
|
||||
this.quote = quote;
|
||||
}
|
||||
|
||||
|
|
|
@ -250,7 +250,10 @@ public class SQLServerDialect extends AbstractTransactSQLDialect {
|
|||
}
|
||||
|
||||
if ( getVersion().isSameOrAfter( 11 ) ) {
|
||||
queryEngine.getSqmFunctionRegistry().register( "format", new SQLServerFormatEmulation( this, queryEngine.getTypeConfiguration() ) );
|
||||
queryEngine.getSqmFunctionRegistry().register(
|
||||
"format",
|
||||
new SQLServerFormatEmulation( this, queryEngine.getTypeConfiguration() )
|
||||
);
|
||||
|
||||
//actually translate() was added in 2017 but
|
||||
//it's not worth adding a new dialect for that!
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.hibernate.boot.model.relational.Exportable;
|
|||
import org.hibernate.boot.model.relational.Sequence;
|
||||
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
|
||||
import org.hibernate.dialect.function.CommonFunctionFactory;
|
||||
import org.hibernate.dialect.function.FormatFunction;
|
||||
import org.hibernate.dialect.lock.LockingStrategy;
|
||||
import org.hibernate.dialect.lock.LockingStrategyException;
|
||||
import org.hibernate.dialect.pagination.LimitHandler;
|
||||
|
@ -434,11 +435,10 @@ public class SpannerDialect extends Dialect {
|
|||
.setExactArgumentCount( 1 )
|
||||
.register();
|
||||
|
||||
queryEngine.getSqmFunctionRegistry().patternDescriptorBuilder("format", "format_timestamp(?2,?1)")
|
||||
.setInvariantType( stringType )
|
||||
.setArgumentsValidator( CommonFunctionFactory.formatValidator() )
|
||||
.setArgumentListSignature("(TIMESTAMP datetime as STRING pattern)")
|
||||
.register();
|
||||
queryEngine.getSqmFunctionRegistry().register(
|
||||
"format",
|
||||
new FormatFunction( "format_timestamp", true, queryEngine.getTypeConfiguration() )
|
||||
);
|
||||
functionFactory.listagg_stringAgg( "string" );
|
||||
functionFactory.inverseDistributionOrderedSetAggregates();
|
||||
functionFactory.hypotheticalOrderedSetAggregates();
|
||||
|
|
|
@ -2212,12 +2212,7 @@ public class CommonFunctionFactory {
|
|||
* H2-style (uses Java's SimpleDateFormat directly so no need to translate format)
|
||||
*/
|
||||
public void format_formatdatetime() {
|
||||
functionRegistry.namedDescriptorBuilder( "format", "formatdatetime" )
|
||||
.setInvariantType(stringType)
|
||||
.setParameterTypes( TEMPORAL, STRING )
|
||||
.setArgumentsValidator( formatValidator() )
|
||||
.setArgumentListSignature( "(TEMPORAL datetime as STRING pattern)" )
|
||||
.register();
|
||||
functionRegistry.register( "format", new FormatFunction( "formatdatetime", typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2226,12 +2221,7 @@ public class CommonFunctionFactory {
|
|||
* @see org.hibernate.dialect.OracleDialect#datetimeFormat
|
||||
*/
|
||||
public void format_toChar() {
|
||||
functionRegistry.namedDescriptorBuilder( "format", "to_char" )
|
||||
.setInvariantType(stringType)
|
||||
.setParameterTypes( TEMPORAL, STRING )
|
||||
.setArgumentsValidator( formatValidator() )
|
||||
.setArgumentListSignature( "(TEMPORAL datetime as STRING pattern)" )
|
||||
.register();
|
||||
functionRegistry.register( "format", new FormatFunction( "to_char", typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2240,12 +2230,7 @@ public class CommonFunctionFactory {
|
|||
* @see org.hibernate.dialect.MySQLDialect#datetimeFormat
|
||||
*/
|
||||
public void format_dateFormat() {
|
||||
functionRegistry.namedDescriptorBuilder( "format", "date_format" )
|
||||
.setInvariantType(stringType)
|
||||
.setParameterTypes( TEMPORAL, STRING )
|
||||
.setArgumentsValidator( formatValidator() )
|
||||
.setArgumentListSignature( "(TEMPORAL datetime as STRING pattern)" )
|
||||
.register();
|
||||
functionRegistry.register( "format", new FormatFunction( "date_format", typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2254,16 +2239,7 @@ public class CommonFunctionFactory {
|
|||
* @see org.hibernate.dialect.OracleDialect#datetimeFormat
|
||||
*/
|
||||
public void format_toVarchar() {
|
||||
functionRegistry.namedDescriptorBuilder( "format", "to_varchar" )
|
||||
.setInvariantType(stringType)
|
||||
.setParameterTypes( TEMPORAL, STRING )
|
||||
.setArgumentsValidator( formatValidator() )
|
||||
.setArgumentListSignature( "(TEMPORAL datetime as STRING pattern)" )
|
||||
.register();
|
||||
}
|
||||
|
||||
public static ArgumentsValidator formatValidator() {
|
||||
return new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL, STRING );
|
||||
functionRegistry.register( "format", new FormatFunction( "to_varchar", typeConfiguration ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,24 +6,18 @@
|
|||
*/
|
||||
package org.hibernate.dialect.function;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.dialect.OracleDialect;
|
||||
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.Format;
|
||||
import org.hibernate.type.StandardBasicTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import java.util.List;
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
|
||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL;
|
||||
|
||||
/**
|
||||
* DB2's varchar_format() can't handle quoted literal strings in
|
||||
* the format pattern. So just split the pattern into bits, call
|
||||
|
@ -33,16 +27,12 @@ import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEM
|
|||
* @author Gavin King
|
||||
*/
|
||||
public class DB2FormatEmulation
|
||||
extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||
extends FormatFunction {
|
||||
|
||||
public DB2FormatEmulation(TypeConfiguration typeConfiguration) {
|
||||
super(
|
||||
"format",
|
||||
CommonFunctionFactory.formatValidator(),
|
||||
StandardFunctionReturnTypeResolvers.invariant(
|
||||
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
|
||||
),
|
||||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TEMPORAL, STRING )
|
||||
typeConfiguration
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -94,9 +84,4 @@ public class DB2FormatEmulation
|
|||
}
|
||||
sqlAppender.appendSql(")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArgumentListSignature() {
|
||||
return "(TEMPORAL datetime as STRING pattern)";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,17 +13,26 @@ import org.hibernate.query.sqm.TemporalUnit;
|
|||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.function.FunctionRenderingSupport;
|
||||
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
import org.hibernate.query.sqm.produce.function.internal.PatternRenderer;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.expression.*;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.ExtractUnit;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
|
||||
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
@ -31,7 +40,6 @@ import static org.hibernate.query.sqm.BinaryArithmeticOperator.*;
|
|||
import static org.hibernate.query.sqm.TemporalUnit.*;
|
||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL;
|
||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL_UNIT;
|
||||
import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers.useArgType;
|
||||
|
||||
/**
|
||||
* ANSI SQL-inspired {@code extract()} function, where the date/time fields
|
||||
|
@ -41,7 +49,7 @@ import static org.hibernate.query.sqm.produce.function.StandardFunctionReturnTyp
|
|||
* @author Gavin King
|
||||
*/
|
||||
public class ExtractFunction
|
||||
extends AbstractSqmFunctionDescriptor {
|
||||
extends AbstractSqmFunctionDescriptor implements FunctionRenderingSupport {
|
||||
|
||||
private final Dialect dialect;
|
||||
|
||||
|
@ -58,14 +66,27 @@ public class ExtractFunction
|
|||
this.dialect = dialect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
SqlAstTranslator<?> walker) {
|
||||
final ExtractUnit field = (ExtractUnit) sqlAstArguments.get( 0 );
|
||||
final TemporalUnit unit = field.getUnit();
|
||||
final String pattern = dialect.extractPattern( unit );
|
||||
new PatternRenderer( pattern ).render( sqlAppender, sqlAstArguments, walker );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> SelfRenderingSqmFunction generateSqmFunctionExpression(
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
ReturnableType<T> impliedResultType,
|
||||
QueryEngine queryEngine,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
SqmExtractUnit<?> field = (SqmExtractUnit<?>) arguments.get(0);
|
||||
SqmExpression<?> expression = (SqmExpression<?>) arguments.get(1);
|
||||
final SqmExtractUnit<?> field = (SqmExtractUnit<?>) arguments.get( 0 );
|
||||
final SqmExpression<?> originalExpression = (SqmExpression<?>) arguments.get( 1 );
|
||||
final boolean compositeTemporal = SqmExpressionHelper.isCompositeTemporal( originalExpression );
|
||||
final SqmExpression<?> expression = SqmExpressionHelper.getOffsetAdjustedExpression( originalExpression );
|
||||
|
||||
TemporalUnit unit = field.getUnit();
|
||||
switch ( unit ) {
|
||||
|
@ -74,8 +95,27 @@ public class ExtractFunction
|
|||
case NATIVE:
|
||||
throw new SemanticException("can't extract() the field TemporalUnit.NATIVE");
|
||||
case OFFSET:
|
||||
// use format(arg, 'xxx') to get the offset
|
||||
return extractOffsetUsingFormat( expression, queryEngine, typeConfiguration );
|
||||
if ( compositeTemporal ) {
|
||||
final SqmPath<Object> offsetPath = ( (SqmPath<?>) originalExpression ).get(
|
||||
AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME
|
||||
);
|
||||
return new SelfRenderingSqmFunction<>(
|
||||
this,
|
||||
(sqlAppender, sqlAstArguments, walker) -> {
|
||||
sqlAstArguments.get( 0 ).accept( walker );
|
||||
},
|
||||
Collections.singletonList( offsetPath ),
|
||||
null,
|
||||
null,
|
||||
StandardFunctionReturnTypeResolvers.useArgType( 1 ),
|
||||
expression.nodeBuilder(),
|
||||
"extract"
|
||||
);
|
||||
}
|
||||
else {
|
||||
// use format(arg, 'xxx') to get the offset
|
||||
return extractOffsetUsingFormat( expression, queryEngine, typeConfiguration );
|
||||
}
|
||||
case DATE:
|
||||
case TIME:
|
||||
// use cast(arg as Type) to get the date or time part
|
||||
|
@ -93,18 +133,16 @@ public class ExtractFunction
|
|||
// otherwise it's something we expect the SQL dialect
|
||||
// itself to understand, either natively, or via the
|
||||
// method Dialect.extract()
|
||||
String pattern = dialect.extractPattern( unit );
|
||||
return queryEngine.getSqmFunctionRegistry()
|
||||
.patternDescriptorBuilder( "extract", pattern )
|
||||
.setExactArgumentCount( 2 )
|
||||
.setReturnTypeResolver( useArgType( 1 ) )
|
||||
.descriptor()
|
||||
.generateSqmExpression(
|
||||
arguments,
|
||||
impliedResultType,
|
||||
queryEngine,
|
||||
typeConfiguration
|
||||
);
|
||||
return new SelfRenderingSqmFunction(
|
||||
this,
|
||||
this,
|
||||
expression == originalExpression ? arguments : List.of( arguments.get( 0 ), expression ),
|
||||
impliedResultType,
|
||||
getArgumentsValidator(),
|
||||
getReturnTypeResolver(),
|
||||
expression.nodeBuilder(),
|
||||
"extract"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,763 @@
|
|||
/*
|
||||
* 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.dialect.function;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.sqm.BinaryArithmeticOperator;
|
||||
import org.hibernate.query.sqm.ComparisonOperator;
|
||||
import org.hibernate.query.sqm.TemporalUnit;
|
||||
import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.function.FunctionRenderingSupport;
|
||||
import org.hibernate.query.sqm.function.MultipatternSqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
|
||||
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
|
||||
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
|
||||
import org.hibernate.query.sqm.produce.function.ArgumentTypesValidator;
|
||||
import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionArgumentTypeResolvers;
|
||||
import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers;
|
||||
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
|
||||
import org.hibernate.query.sqm.tree.SqmTypedNode;
|
||||
import org.hibernate.sql.ast.SqlAstTranslator;
|
||||
import org.hibernate.sql.ast.spi.SqlAppender;
|
||||
import org.hibernate.sql.ast.tree.SqlAstNode;
|
||||
import org.hibernate.sql.ast.tree.expression.BinaryArithmeticExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.CastTarget;
|
||||
import org.hibernate.sql.ast.tree.expression.DurationUnit;
|
||||
import org.hibernate.sql.ast.tree.expression.Expression;
|
||||
import org.hibernate.sql.ast.tree.expression.Format;
|
||||
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
|
||||
import org.hibernate.sql.ast.tree.predicate.BetweenPredicate;
|
||||
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.StandardBasicTypes;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
|
||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.STRING;
|
||||
import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL;
|
||||
|
||||
/**
|
||||
* A format function with support for composite temporal expressions.
|
||||
*
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
public class FormatFunction extends AbstractSqmFunctionDescriptor implements FunctionRenderingSupport {
|
||||
|
||||
private final String nativeFunctionName;
|
||||
private final boolean reversedArguments;
|
||||
|
||||
public FormatFunction(String nativeFunctionName, TypeConfiguration typeConfiguration) {
|
||||
this( nativeFunctionName, false, typeConfiguration );
|
||||
}
|
||||
|
||||
public FormatFunction(String nativeFunctionName, boolean reversedArguments, TypeConfiguration typeConfiguration) {
|
||||
super(
|
||||
"format",
|
||||
new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL, STRING ),
|
||||
StandardFunctionReturnTypeResolvers.invariant( typeConfiguration.getBasicTypeRegistry().resolve(
|
||||
StandardBasicTypes.STRING ) ),
|
||||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TEMPORAL, STRING )
|
||||
);
|
||||
this.nativeFunctionName = nativeFunctionName;
|
||||
this.reversedArguments = reversedArguments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(
|
||||
SqlAppender sqlAppender,
|
||||
List<? extends SqlAstNode> sqlAstArguments,
|
||||
SqlAstTranslator<?> walker) {
|
||||
sqlAppender.appendSql( nativeFunctionName );
|
||||
sqlAppender.append( '(' );
|
||||
final SqlAstNode expression = sqlAstArguments.get( 0 );
|
||||
final SqlAstNode format = sqlAstArguments.get( 1 );
|
||||
if ( reversedArguments ) {
|
||||
format.accept( walker );
|
||||
sqlAppender.append( ',' );
|
||||
expression.accept( walker );
|
||||
}
|
||||
else {
|
||||
expression.accept( walker );
|
||||
sqlAppender.append( ',' );
|
||||
format.accept( walker );
|
||||
}
|
||||
sqlAppender.append( ')' );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> SelfRenderingSqmFunction<T> generateSqmFunctionExpression(
|
||||
List<? extends SqmTypedNode<?>> arguments,
|
||||
ReturnableType<T> impliedResultType,
|
||||
QueryEngine queryEngine,
|
||||
TypeConfiguration typeConfiguration) {
|
||||
return new SelfRenderingSqmFunction<>(
|
||||
this,
|
||||
this,
|
||||
arguments,
|
||||
impliedResultType,
|
||||
getArgumentsValidator(),
|
||||
getReturnTypeResolver(),
|
||||
queryEngine.getCriteriaBuilder(),
|
||||
"format"
|
||||
) {
|
||||
@Override
|
||||
public Expression convertToSqlAst(SqmToSqlAstConverter walker) {
|
||||
final ReturnableType<?> resultType = resolveResultType(
|
||||
walker.getCreationContext().getMappingMetamodel().getTypeConfiguration()
|
||||
);
|
||||
|
||||
final List<SqlAstNode> arguments = resolveSqlAstArguments( getArguments(), walker );
|
||||
final SqlAstNode expression = arguments.get( 0 );
|
||||
if ( expression instanceof SqlTupleContainer ) {
|
||||
// SqlTupleContainer means this is a composite temporal type i.e. uses `@TimeZoneStorage(COLUMN)`
|
||||
// The support for this kind of type requires that we inject the offset from the second column
|
||||
// as literal into the pattern, and apply the formatting on the date time part
|
||||
final SqlTuple sqlTuple = ( (SqlTupleContainer) expression ).getSqlTuple();
|
||||
final AbstractSqmSelfRenderingFunctionDescriptor timestampaddFunction = getFunction(
|
||||
walker,
|
||||
"timestampadd"
|
||||
);
|
||||
final BasicType<Integer> integerType = typeConfiguration.getBasicTypeRegistry()
|
||||
.resolve( StandardBasicTypes.INTEGER );
|
||||
arguments.set( 0, getOffsetAdjusted( sqlTuple, timestampaddFunction, integerType ) );
|
||||
final Format format = (Format) arguments.get( 1 );
|
||||
// If the format contains a time zone or offset, we must replace that with the offset column
|
||||
if ( format.getFormat().contains( "x" ) ) {
|
||||
final AbstractSqmSelfRenderingFunctionDescriptor concatFunction = getFunction(
|
||||
walker,
|
||||
"concat"
|
||||
);
|
||||
final AbstractSqmSelfRenderingFunctionDescriptor substringFunction = getFunction(
|
||||
walker,
|
||||
"substring",
|
||||
3
|
||||
);
|
||||
final AbstractSqmSelfRenderingFunctionDescriptor floorFunction = getFunction( walker, "floor" );
|
||||
final AbstractSqmSelfRenderingFunctionDescriptor castFunction = getFunction( walker, "cast" );
|
||||
final BasicType<String> stringType = typeConfiguration.getBasicTypeRegistry()
|
||||
.resolve( StandardBasicTypes.STRING );
|
||||
final Dialect dialect = walker.getCreationContext()
|
||||
.getSessionFactory()
|
||||
.getJdbcServices()
|
||||
.getDialect();
|
||||
Expression formatExpression = null;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
dialect.appendDatetimeFormat( sb::append, "'a'" );
|
||||
final String delimiter = sb.substring( 0, sb.indexOf( "a" ) ).replace( "''", "'" );
|
||||
final String[] chunks = StringHelper.splitFull( "'", format.getFormat() );
|
||||
final Expression offsetExpression = sqlTuple.getExpressions().get( 1 );
|
||||
// Splitting by `'` will put actual format pattern parts to even indices and literal pattern parts
|
||||
// to uneven indices. We will only replace the time zone and offset pattern in the format pattern parts
|
||||
for ( int i = 0; i < chunks.length; i += 2 ) {
|
||||
// The general idea is to replace the various patterns `xxx`, `xx` and `x` by concatenating
|
||||
// the offset column as literal i.e. `HH:mmxxx` is translated to `HH:mm'''||offset||'''`
|
||||
// xxx stands for the full offset i.e. `+01:00`
|
||||
// xx stands for the medium offset i.e. `+0100`
|
||||
// x stands for the small offset i.e. `+01`
|
||||
final String[] fullParts = StringHelper.splitFull( "xxx", chunks[i] );
|
||||
for ( int j = 0; j < fullParts.length; j++ ) {
|
||||
if ( fullParts[j].isEmpty() ) {
|
||||
continue;
|
||||
}
|
||||
final String[] mediumParts = StringHelper.splitFull( "xx", fullParts[j] );
|
||||
for ( int k = 0; k < mediumParts.length; k++ ) {
|
||||
if ( mediumParts[k].isEmpty() ) {
|
||||
continue;
|
||||
}
|
||||
final String[] smallParts = StringHelper.splitFull( "x", mediumParts[k] );
|
||||
for ( int l = 0; l < smallParts.length; l++ ) {
|
||||
if ( smallParts[l].isEmpty() ) {
|
||||
continue;
|
||||
}
|
||||
sb.setLength( 0 );
|
||||
dialect.appendDatetimeFormat( sb::append, smallParts[l] );
|
||||
final String formatPart = sb.toString();
|
||||
formatExpression = concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
formatExpression,
|
||||
new QueryLiteral<>( formatPart, stringType )
|
||||
);
|
||||
if ( l + 1 < smallParts.length ) {
|
||||
// This is for `x` patterns, which require `+01`
|
||||
// so we concat `substring(offset, 1, 4)`
|
||||
// Since the offset is always in the full format
|
||||
formatExpression = concatAsLiteral(
|
||||
concatFunction,
|
||||
stringType,
|
||||
delimiter,
|
||||
formatExpression,
|
||||
createSmallOffset(
|
||||
concatFunction,
|
||||
substringFunction,
|
||||
floorFunction,
|
||||
castFunction,
|
||||
stringType,
|
||||
integerType,
|
||||
offsetExpression
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if ( k + 1 < mediumParts.length ) {
|
||||
// This is for `xx` patterns, which require `+0100`
|
||||
// so we concat `substring(offset, 1, 4)||substring(offset, 4, 6)`
|
||||
// Since the offset is always in the full format
|
||||
formatExpression = concatAsLiteral(
|
||||
concatFunction,
|
||||
stringType,
|
||||
delimiter, formatExpression,
|
||||
createMediumOffset(
|
||||
concatFunction,
|
||||
substringFunction,
|
||||
floorFunction,
|
||||
castFunction,
|
||||
stringType,
|
||||
integerType,
|
||||
offsetExpression
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if ( j + 1 < fullParts.length ) {
|
||||
formatExpression = concatAsLiteral(
|
||||
concatFunction,
|
||||
stringType,
|
||||
delimiter, formatExpression,
|
||||
createFullOffset(
|
||||
concatFunction,
|
||||
floorFunction,
|
||||
castFunction,
|
||||
stringType,
|
||||
integerType,
|
||||
offsetExpression
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( i + 1 < chunks.length ) {
|
||||
// Handle the pattern literal content
|
||||
sb.setLength( 0 );
|
||||
dialect.appendDatetimeFormat( sb::append, "'" + chunks[i + 1] + "'" );
|
||||
final String formatLiteralPart = sb.toString().replace( "''", "'" );
|
||||
formatExpression = concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
formatExpression,
|
||||
new QueryLiteral<>(
|
||||
formatLiteralPart,
|
||||
stringType
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
arguments.set( 1, formatExpression );
|
||||
}
|
||||
}
|
||||
if ( getArgumentsValidator() != null ) {
|
||||
getArgumentsValidator().validateSqlTypes( arguments, getFunctionName() );
|
||||
}
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
getFunctionName(),
|
||||
getRenderingSupport(),
|
||||
arguments,
|
||||
resultType,
|
||||
resultType == null ? null : getMappingModelExpressible( walker, resultType )
|
||||
);
|
||||
}
|
||||
|
||||
private AbstractSqmSelfRenderingFunctionDescriptor getFunction(SqmToSqlAstConverter walker, String name) {
|
||||
return (AbstractSqmSelfRenderingFunctionDescriptor) walker.getCreationContext()
|
||||
.getSessionFactory()
|
||||
.getQueryEngine()
|
||||
.getSqmFunctionRegistry()
|
||||
.findFunctionDescriptor( name );
|
||||
}
|
||||
|
||||
private AbstractSqmSelfRenderingFunctionDescriptor getFunction(SqmToSqlAstConverter walker, String name, int argumentCount) {
|
||||
final SqmFunctionDescriptor functionDescriptor = walker.getCreationContext()
|
||||
.getSessionFactory()
|
||||
.getQueryEngine()
|
||||
.getSqmFunctionRegistry()
|
||||
.findFunctionDescriptor( name );
|
||||
if ( functionDescriptor instanceof MultipatternSqmFunctionDescriptor ) {
|
||||
return (AbstractSqmSelfRenderingFunctionDescriptor) ( (MultipatternSqmFunctionDescriptor) functionDescriptor )
|
||||
.getFunction( argumentCount );
|
||||
}
|
||||
return (AbstractSqmSelfRenderingFunctionDescriptor) functionDescriptor;
|
||||
}
|
||||
|
||||
private SqlAstNode getOffsetAdjusted(
|
||||
SqlTuple sqlTuple,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor timestampaddFunction,
|
||||
BasicType<Integer> integerType) {
|
||||
final Expression instantExpression = sqlTuple.getExpressions().get( 0 );
|
||||
final Expression offsetExpression = sqlTuple.getExpressions().get( 1 );
|
||||
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
"timestampadd",
|
||||
timestampaddFunction,
|
||||
List.of(
|
||||
new DurationUnit( TemporalUnit.SECOND, integerType ),
|
||||
offsetExpression,
|
||||
instantExpression
|
||||
),
|
||||
(ReturnableType<?>) instantExpression.getExpressionType(),
|
||||
instantExpression.getExpressionType()
|
||||
);
|
||||
}
|
||||
|
||||
private Expression createFullOffset(
|
||||
AbstractSqmSelfRenderingFunctionDescriptor concatFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
|
||||
BasicType<String> stringType,
|
||||
BasicType<Integer> integerType,
|
||||
Expression offsetExpression) {
|
||||
if ( offsetExpression.getExpressionType().getJdbcMappings().get( 0 ).getJdbcType().isString() ) {
|
||||
return offsetExpression;
|
||||
}
|
||||
else {
|
||||
// ZoneOffset as seconds
|
||||
final CaseSearchedExpression caseSearchedExpression = new CaseSearchedExpression( stringType );
|
||||
caseSearchedExpression.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new ComparisonPredicate(
|
||||
offsetExpression,
|
||||
ComparisonOperator.LESS_THAN_OR_EQUAL,
|
||||
new QueryLiteral<>(
|
||||
-36000,
|
||||
integerType
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( "-", stringType )
|
||||
)
|
||||
);
|
||||
caseSearchedExpression.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new ComparisonPredicate(
|
||||
offsetExpression,
|
||||
ComparisonOperator.LESS_THAN,
|
||||
new QueryLiteral<>(
|
||||
0,
|
||||
integerType
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( "-0", stringType )
|
||||
)
|
||||
);
|
||||
caseSearchedExpression.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new ComparisonPredicate(
|
||||
offsetExpression,
|
||||
ComparisonOperator.GREATER_THAN_OR_EQUAL,
|
||||
new QueryLiteral<>(
|
||||
36000,
|
||||
integerType
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( "+", stringType )
|
||||
)
|
||||
);
|
||||
caseSearchedExpression.otherwise( new QueryLiteral<>( "+0", stringType ) );
|
||||
final Expression hours = getHours( floorFunction, castFunction, integerType, offsetExpression );
|
||||
final Expression minutes = getMinutes( floorFunction, castFunction, integerType, offsetExpression );
|
||||
|
||||
final CaseSearchedExpression minuteStart = new CaseSearchedExpression( stringType );
|
||||
minuteStart.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new BetweenPredicate(
|
||||
minutes,
|
||||
new QueryLiteral<>(
|
||||
-9,
|
||||
integerType
|
||||
),
|
||||
new QueryLiteral<>(
|
||||
9,
|
||||
integerType
|
||||
),
|
||||
false,
|
||||
null
|
||||
),
|
||||
new QueryLiteral<>( ":0", stringType )
|
||||
)
|
||||
);
|
||||
minuteStart.otherwise( new QueryLiteral<>( ":", stringType ) );
|
||||
return concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
concat( concatFunction, stringType, caseSearchedExpression, hours ),
|
||||
minuteStart
|
||||
),
|
||||
minutes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Expression createMediumOffset(
|
||||
AbstractSqmSelfRenderingFunctionDescriptor concatFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor substringFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
|
||||
BasicType<String> stringType,
|
||||
BasicType<Integer> integerType,
|
||||
Expression offsetExpression) {
|
||||
if ( offsetExpression.getExpressionType().getJdbcMappings().get( 0 ).getJdbcType().isString() ) {
|
||||
return concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
createSmallOffset(
|
||||
concatFunction,
|
||||
substringFunction,
|
||||
floorFunction,
|
||||
castFunction,
|
||||
stringType,
|
||||
integerType,
|
||||
offsetExpression
|
||||
),
|
||||
new SelfRenderingFunctionSqlAstExpression(
|
||||
"substring",
|
||||
substringFunction,
|
||||
List.of(
|
||||
offsetExpression,
|
||||
new QueryLiteral<>( 4, integerType ),
|
||||
new QueryLiteral<>( 6, integerType )
|
||||
),
|
||||
stringType,
|
||||
stringType
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
// ZoneOffset as seconds
|
||||
final CaseSearchedExpression caseSearchedExpression = new CaseSearchedExpression( stringType );
|
||||
caseSearchedExpression.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new ComparisonPredicate(
|
||||
offsetExpression,
|
||||
ComparisonOperator.LESS_THAN_OR_EQUAL,
|
||||
new QueryLiteral<>(
|
||||
-36000,
|
||||
integerType
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( "-", stringType )
|
||||
)
|
||||
);
|
||||
caseSearchedExpression.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new ComparisonPredicate(
|
||||
offsetExpression,
|
||||
ComparisonOperator.LESS_THAN,
|
||||
new QueryLiteral<>(
|
||||
0,
|
||||
integerType
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( "-0", stringType )
|
||||
)
|
||||
);
|
||||
caseSearchedExpression.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new ComparisonPredicate(
|
||||
offsetExpression,
|
||||
ComparisonOperator.GREATER_THAN_OR_EQUAL,
|
||||
new QueryLiteral<>(
|
||||
36000,
|
||||
integerType
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( "+", stringType )
|
||||
)
|
||||
);
|
||||
caseSearchedExpression.otherwise( new QueryLiteral<>( "+0", stringType ) );
|
||||
|
||||
final Expression hours = getHours( floorFunction, castFunction, integerType, offsetExpression );
|
||||
final Expression minutes = getMinutes( floorFunction, castFunction, integerType, offsetExpression );
|
||||
|
||||
final CaseSearchedExpression minuteStart = new CaseSearchedExpression( stringType );
|
||||
minuteStart.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new BetweenPredicate(
|
||||
minutes,
|
||||
new QueryLiteral<>(
|
||||
-9,
|
||||
integerType
|
||||
),
|
||||
new QueryLiteral<>(
|
||||
9,
|
||||
integerType
|
||||
),
|
||||
false,
|
||||
null
|
||||
),
|
||||
new QueryLiteral<>( "0", stringType )
|
||||
)
|
||||
);
|
||||
minuteStart.otherwise( new QueryLiteral<>( "", stringType ) );
|
||||
return concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
concat( concatFunction, stringType, caseSearchedExpression, hours ),
|
||||
minuteStart
|
||||
),
|
||||
minutes
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Expression createSmallOffset(
|
||||
AbstractSqmSelfRenderingFunctionDescriptor concatFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor substringFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
|
||||
BasicType<String> stringType,
|
||||
BasicType<Integer> integerType,
|
||||
Expression offsetExpression) {
|
||||
if ( offsetExpression.getExpressionType().getJdbcMappings().get( 0 ).getJdbcType().isString() ) {
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
"substring",
|
||||
substringFunction,
|
||||
List.of(
|
||||
offsetExpression,
|
||||
new QueryLiteral<>( 1, integerType ),
|
||||
new QueryLiteral<>( 4, integerType )
|
||||
),
|
||||
stringType,
|
||||
stringType
|
||||
);
|
||||
}
|
||||
else {
|
||||
// ZoneOffset as seconds
|
||||
final CaseSearchedExpression caseSearchedExpression = new CaseSearchedExpression( stringType );
|
||||
caseSearchedExpression.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new ComparisonPredicate(
|
||||
offsetExpression,
|
||||
ComparisonOperator.LESS_THAN_OR_EQUAL,
|
||||
new QueryLiteral<>(
|
||||
-36000,
|
||||
integerType
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( "-", stringType )
|
||||
)
|
||||
);
|
||||
caseSearchedExpression.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new ComparisonPredicate(
|
||||
offsetExpression,
|
||||
ComparisonOperator.LESS_THAN,
|
||||
new QueryLiteral<>(
|
||||
0,
|
||||
integerType
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( "-0", stringType )
|
||||
)
|
||||
);
|
||||
caseSearchedExpression.getWhenFragments().add(
|
||||
new CaseSearchedExpression.WhenFragment(
|
||||
new ComparisonPredicate(
|
||||
offsetExpression,
|
||||
ComparisonOperator.GREATER_THAN_OR_EQUAL,
|
||||
new QueryLiteral<>(
|
||||
36000,
|
||||
integerType
|
||||
)
|
||||
),
|
||||
new QueryLiteral<>( "+", stringType )
|
||||
)
|
||||
);
|
||||
caseSearchedExpression.otherwise( new QueryLiteral<>( "+0", stringType ) );
|
||||
final Expression hours = getHours( floorFunction, castFunction, integerType, offsetExpression );
|
||||
return concat( concatFunction, stringType, caseSearchedExpression, hours );
|
||||
}
|
||||
}
|
||||
|
||||
private Expression concatAsLiteral(
|
||||
AbstractSqmSelfRenderingFunctionDescriptor concatFunction,
|
||||
BasicType<String> stringType,
|
||||
String delimiter,
|
||||
Expression expression,
|
||||
Expression expression2) {
|
||||
return concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
concat(
|
||||
concatFunction,
|
||||
stringType,
|
||||
expression,
|
||||
new QueryLiteral<>( delimiter, stringType )
|
||||
),
|
||||
expression2
|
||||
),
|
||||
new QueryLiteral<>( delimiter, stringType )
|
||||
);
|
||||
}
|
||||
|
||||
private Expression concat(
|
||||
AbstractSqmSelfRenderingFunctionDescriptor concatFunction,
|
||||
BasicType<String> stringType,
|
||||
Expression expression,
|
||||
Expression expression2) {
|
||||
if ( expression == null ) {
|
||||
return expression2;
|
||||
}
|
||||
else if ( expression instanceof SelfRenderingFunctionSqlAstExpression
|
||||
&& "concat".equals( ( (SelfRenderingFunctionSqlAstExpression) expression ).getFunctionName() ) ) {
|
||||
List<SqlAstNode> list = (List<SqlAstNode>) ( (SelfRenderingFunctionSqlAstExpression) expression ).getArguments();
|
||||
final SqlAstNode lastOperand = list.get( list.size() - 1 );
|
||||
if ( expression2 instanceof QueryLiteral<?> && lastOperand instanceof QueryLiteral<?> ) {
|
||||
list.set(
|
||||
list.size() - 1,
|
||||
new QueryLiteral<>(
|
||||
( (QueryLiteral<?>) lastOperand ).getLiteralValue().toString() +
|
||||
( (QueryLiteral<?>) expression2 ).getLiteralValue().toString(),
|
||||
stringType
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
list.add( expression2 );
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
else if ( expression2 instanceof SelfRenderingFunctionSqlAstExpression
|
||||
&& "concat".equals( ( (SelfRenderingFunctionSqlAstExpression) expression2 ).getFunctionName() ) ) {
|
||||
List<SqlAstNode> list = (List<SqlAstNode>) ( (SelfRenderingFunctionSqlAstExpression) expression2 ).getArguments();
|
||||
final SqlAstNode firstOperand = list.get( 0 );
|
||||
if ( expression instanceof QueryLiteral<?> && firstOperand instanceof QueryLiteral<?> ) {
|
||||
list.set(
|
||||
list.size() - 1,
|
||||
new QueryLiteral<>(
|
||||
( (QueryLiteral<?>) expression ).getLiteralValue().toString() +
|
||||
( (QueryLiteral<?>) firstOperand ).getLiteralValue().toString(),
|
||||
stringType
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
list.add( 0, expression );
|
||||
}
|
||||
return expression2;
|
||||
}
|
||||
else if ( expression instanceof QueryLiteral<?> && expression2 instanceof QueryLiteral<?> ) {
|
||||
return new QueryLiteral<>(
|
||||
( (QueryLiteral<?>) expression ).getLiteralValue().toString() +
|
||||
( (QueryLiteral<?>) expression2 ).getLiteralValue().toString(),
|
||||
stringType
|
||||
);
|
||||
}
|
||||
else {
|
||||
final List<Expression> list = new ArrayList<>( 2 );
|
||||
list.add( expression );
|
||||
list.add( expression2 );
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
"concat",
|
||||
concatFunction,
|
||||
list,
|
||||
stringType,
|
||||
stringType
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Expression getHours(
|
||||
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
|
||||
BasicType<Integer> integerType,
|
||||
Expression offsetExpression) {
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
"cast",
|
||||
castFunction,
|
||||
List.of(
|
||||
new SelfRenderingFunctionSqlAstExpression(
|
||||
"floor",
|
||||
floorFunction,
|
||||
List.of(
|
||||
new BinaryArithmeticExpression(
|
||||
offsetExpression,
|
||||
BinaryArithmeticOperator.DIVIDE,
|
||||
new QueryLiteral<>( 3600, integerType ),
|
||||
integerType
|
||||
)
|
||||
),
|
||||
integerType,
|
||||
integerType
|
||||
),
|
||||
new CastTarget( integerType )
|
||||
),
|
||||
integerType,
|
||||
integerType
|
||||
);
|
||||
}
|
||||
|
||||
private Expression getMinutes(
|
||||
AbstractSqmSelfRenderingFunctionDescriptor floorFunction,
|
||||
AbstractSqmSelfRenderingFunctionDescriptor castFunction,
|
||||
BasicType<Integer> integerType,
|
||||
Expression offsetExpression){
|
||||
return new SelfRenderingFunctionSqlAstExpression(
|
||||
"cast",
|
||||
castFunction,
|
||||
List.of(
|
||||
new SelfRenderingFunctionSqlAstExpression(
|
||||
"floor",
|
||||
floorFunction,
|
||||
List.of(
|
||||
new BinaryArithmeticExpression(
|
||||
new BinaryArithmeticExpression(
|
||||
offsetExpression,
|
||||
BinaryArithmeticOperator.MODULO,
|
||||
new QueryLiteral<>( 3600, integerType ),
|
||||
integerType
|
||||
),
|
||||
BinaryArithmeticOperator.DIVIDE,
|
||||
new QueryLiteral<>( 60, integerType ),
|
||||
integerType
|
||||
)
|
||||
),
|
||||
integerType,
|
||||
integerType
|
||||
),
|
||||
new CastTarget( integerType )
|
||||
),
|
||||
integerType,
|
||||
integerType
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArgumentListSignature() {
|
||||
return "(TEMPORAL datetime as STRING pattern)";
|
||||
}
|
||||
|
||||
}
|
|
@ -29,19 +29,12 @@ import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEM
|
|||
*
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
public class SQLServerFormatEmulation extends AbstractSqmSelfRenderingFunctionDescriptor {
|
||||
public class SQLServerFormatEmulation extends FormatFunction {
|
||||
|
||||
private final SQLServerDialect dialect;
|
||||
|
||||
public SQLServerFormatEmulation(SQLServerDialect dialect, TypeConfiguration typeConfiguration) {
|
||||
super(
|
||||
"format",
|
||||
CommonFunctionFactory.formatValidator(),
|
||||
StandardFunctionReturnTypeResolvers.invariant(
|
||||
typeConfiguration.getBasicTypeRegistry().resolve( StandardBasicTypes.STRING )
|
||||
),
|
||||
StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TEMPORAL, STRING )
|
||||
);
|
||||
super( "format", typeConfiguration );
|
||||
this.dialect = dialect;
|
||||
}
|
||||
|
||||
|
@ -52,7 +45,6 @@ public class SQLServerFormatEmulation extends AbstractSqmSelfRenderingFunctionDe
|
|||
SqlAstTranslator<?> walker) {
|
||||
final Expression datetime = (Expression) arguments.get(0);
|
||||
final boolean isTime = TypeConfiguration.getSqlTemporalType( datetime.getExpressionType() ) == TemporalType.TIME;
|
||||
final Format format = (Format) arguments.get(1);
|
||||
|
||||
sqlAppender.appendSql("format(");
|
||||
if ( isTime ) {
|
||||
|
@ -63,13 +55,8 @@ public class SQLServerFormatEmulation extends AbstractSqmSelfRenderingFunctionDe
|
|||
else {
|
||||
datetime.accept( walker );
|
||||
}
|
||||
sqlAppender.appendSql(",'");
|
||||
dialect.appendDatetimeFormat( sqlAppender, format.getFormat() );
|
||||
sqlAppender.appendSql("')");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getArgumentListSignature() {
|
||||
return "(TEMPORAL datetime as STRING pattern)";
|
||||
sqlAppender.appendSql(',');
|
||||
arguments.get( 1 ).accept( walker );
|
||||
sqlAppender.appendSql(')');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
package org.hibernate.internal.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
|
@ -336,6 +337,18 @@ public final class StringHelper {
|
|||
return result;
|
||||
}
|
||||
|
||||
public static String[] splitFull(String separators, String list) {
|
||||
final List<String> parts = new ArrayList<>();
|
||||
int prevIndex = 0;
|
||||
int index;
|
||||
while ( ( index = list.indexOf( separators, prevIndex ) ) != -1 ) {
|
||||
parts.add( list.substring( prevIndex, index ) );
|
||||
prevIndex = index + separators.length();
|
||||
}
|
||||
parts.add( list.substring( prevIndex ) );
|
||||
return parts.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public static String unqualify(String qualifiedName) {
|
||||
int loc = qualifiedName.lastIndexOf( '.' );
|
||||
return ( loc < 0 ) ? qualifiedName : qualifiedName.substring( loc + 1 );
|
||||
|
|
|
@ -683,6 +683,8 @@ public class BasicValue extends SimpleValue implements JdbcTypeIndicators, Resol
|
|||
public TimeZoneStorageStrategy getDefaultTimeZoneStorageStrategy() {
|
||||
if ( timeZoneStorageType != null ) {
|
||||
switch ( timeZoneStorageType ) {
|
||||
case COLUMN:
|
||||
return TimeZoneStorageStrategy.COLUMN;
|
||||
case NATIVE:
|
||||
return TimeZoneStorageStrategy.NATIVE;
|
||||
case NORMALIZE:
|
||||
|
|
|
@ -103,4 +103,8 @@ public class MultipatternSqmFunctionDescriptor extends AbstractSqmFunctionDescri
|
|||
public void setArgumentListSignature(String argumentListSignature) {
|
||||
this.argumentListSignature = argumentListSignature;
|
||||
}
|
||||
|
||||
public SqmFunctionDescriptor getFunction(int argumentCount) {
|
||||
return functions[argumentCount];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
|
|||
import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource;
|
||||
import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource;
|
||||
import org.hibernate.metamodel.model.domain.internal.DiscriminatorSqmPath;
|
||||
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
|
||||
import org.hibernate.metamodel.model.domain.internal.EntityTypeImpl;
|
||||
import org.hibernate.persister.entity.AbstractEntityPersister;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
@ -189,6 +190,7 @@ import org.hibernate.query.sqm.tree.expression.SqmDurationUnit;
|
|||
import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmEvery;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpressionHelper;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExtractUnit;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmFormat;
|
||||
|
@ -305,6 +307,7 @@ import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression;
|
|||
import org.hibernate.sql.ast.tree.expression.SelfRenderingSqlFragmentExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTuple;
|
||||
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
|
||||
import org.hibernate.sql.ast.tree.expression.Star;
|
||||
import org.hibernate.sql.ast.tree.expression.Summarization;
|
||||
import org.hibernate.sql.ast.tree.expression.TrimSpecification;
|
||||
|
@ -316,6 +319,7 @@ import org.hibernate.sql.ast.tree.from.NamedTableReference;
|
|||
import org.hibernate.sql.ast.tree.from.PluralTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.QueryPartTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
|
||||
import org.hibernate.sql.ast.tree.from.SyntheticVirtualTableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroup;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoin;
|
||||
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
|
||||
|
@ -369,10 +373,12 @@ import org.hibernate.type.SqlTypes;
|
|||
import org.hibernate.type.descriptor.java.BasicJavaType;
|
||||
import org.hibernate.type.descriptor.java.EnumJavaType;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
import org.hibernate.type.descriptor.java.TemporalJavaType;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcType;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
import org.hibernate.usertype.UserVersionType;
|
||||
import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
|
@ -382,7 +388,6 @@ import static org.hibernate.query.sqm.BinaryArithmeticOperator.MULTIPLY;
|
|||
import static org.hibernate.query.sqm.BinaryArithmeticOperator.SUBTRACT;
|
||||
import static org.hibernate.query.sqm.TemporalUnit.DAY;
|
||||
import static org.hibernate.query.sqm.TemporalUnit.EPOCH;
|
||||
import static org.hibernate.query.sqm.TemporalUnit.NATIVE;
|
||||
import static org.hibernate.query.sqm.TemporalUnit.SECOND;
|
||||
import static org.hibernate.query.sqm.UnaryArithmeticOperator.UNARY_MINUS;
|
||||
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
|
||||
|
@ -4687,6 +4692,17 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
// We can't determine the type of the expression
|
||||
return null;
|
||||
}
|
||||
if ( nodeType instanceof EmbeddedSqmPathSource<?> ) {
|
||||
if ( sqmExpression instanceof SqmBinaryArithmetic<?> ) {
|
||||
final SqmBinaryArithmetic<?> binaryArithmetic = (SqmBinaryArithmetic<?>) sqmExpression;
|
||||
if ( binaryArithmetic.getLeftHandOperand().getNodeType() == nodeType ) {
|
||||
return determineValueMapping( binaryArithmetic.getLeftHandOperand(), fromClauseIndex );
|
||||
}
|
||||
else if ( binaryArithmetic.getRightHandOperand().getNodeType() == nodeType ) {
|
||||
return determineValueMapping( binaryArithmetic.getRightHandOperand(), fromClauseIndex );
|
||||
}
|
||||
}
|
||||
}
|
||||
final MappingMetamodel domainModel = creationContext.getSessionFactory()
|
||||
.getRuntimeMetamodels()
|
||||
.getMappingMetamodel();
|
||||
|
@ -5190,7 +5206,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
}
|
||||
|
||||
private Object transformDurationArithmetic(SqmBinaryArithmetic<?> expression) {
|
||||
BinaryArithmeticOperator operator = expression.getOperator();
|
||||
final BinaryArithmeticOperator operator = expression.getOperator();
|
||||
final SqmExpression<?> lhs = SqmExpressionHelper.getActualExpression( expression.getLeftHandOperand() );
|
||||
final SqmExpression<?> rhs = SqmExpressionHelper.getActualExpression( expression.getRightHandOperand() );
|
||||
|
||||
// we have a date or timestamp somewhere to
|
||||
// the right of us, so we need to restructure
|
||||
|
@ -5223,7 +5241,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
|
||||
Expression timestamp = adjustedTimestamp;
|
||||
SqmExpressible<?> timestampType = adjustedTimestampType;
|
||||
adjustedTimestamp = toSqlExpression( expression.getLeftHandOperand().accept( this ) );
|
||||
adjustedTimestamp = toSqlExpression( lhs.accept( this ) );
|
||||
JdbcMappingContainer type = adjustedTimestamp.getExpressionType();
|
||||
if ( type instanceof SqmExpressible) {
|
||||
adjustedTimestampType = (SqmExpressible<?>) type;
|
||||
|
@ -5233,13 +5251,71 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
}
|
||||
else {
|
||||
// else we know it has not been transformed
|
||||
adjustedTimestampType = expression.getLeftHandOperand().getNodeType();
|
||||
adjustedTimestampType = lhs.getNodeType();
|
||||
}
|
||||
if ( operator == SUBTRACT ) {
|
||||
negativeAdjustment = !negativeAdjustment;
|
||||
}
|
||||
try {
|
||||
return expression.getRightHandOperand().accept( this );
|
||||
final Object result = rhs.accept( this );
|
||||
if ( result instanceof SqlTupleContainer ) {
|
||||
return result;
|
||||
}
|
||||
final Object offset;
|
||||
if ( lhs != expression.getLeftHandOperand() ) {
|
||||
offset = ( (SqmPath<?>) expression.getLeftHandOperand() ).get(
|
||||
AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME
|
||||
).accept( this );
|
||||
}
|
||||
else if ( rhs != expression.getRightHandOperand() ) {
|
||||
offset = ( (SqmPath<?>) expression.getRightHandOperand() ).get(
|
||||
AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME
|
||||
).accept( this );
|
||||
}
|
||||
else {
|
||||
offset = null;
|
||||
}
|
||||
if ( offset == null ) {
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
final EmbeddableValuedModelPart valueMapping = (EmbeddableValuedModelPart) determineValueMapping( expression );
|
||||
final SqmPath<?> path = SqmExpressionHelper.findPath( expression, expression.getNodeType() );
|
||||
final FromClauseIndex fromClauseIndex = fromClauseIndexStack.getCurrent();
|
||||
final TableGroup parentTableGroup = fromClauseIndex.findTableGroup(
|
||||
path.getLhs().getNavigablePath()
|
||||
);
|
||||
final NavigablePath navigablePath = parentTableGroup.getNavigablePath().append(
|
||||
path.getNavigablePath().getUnaliasedLocalName(),
|
||||
Long.toString( System.nanoTime() )
|
||||
);
|
||||
final TableGroup tableGroup = new SyntheticVirtualTableGroup(
|
||||
navigablePath,
|
||||
valueMapping,
|
||||
parentTableGroup
|
||||
);
|
||||
fromClauseIndex.registerTableGroup( navigablePath, tableGroup );
|
||||
// Register the expressions under the column reference key
|
||||
final SqlExpressionResolver resolver = getSqlAstCreationState().getSqlExpressionResolver();
|
||||
final TableReference tableReference = tableGroup.getPrimaryTableReference();
|
||||
valueMapping.forEachSelectable(
|
||||
(selectionIndex, selection) -> {
|
||||
resolver.resolveSqlExpression(
|
||||
SqlExpressionResolver.createColumnReferenceKey(
|
||||
tableReference,
|
||||
selection.getSelectionExpression()
|
||||
),
|
||||
processingState -> (Expression) (selectionIndex == 0 ? result : offset)
|
||||
);
|
||||
}
|
||||
);
|
||||
return new EmbeddableValuedPathInterpretation<>(
|
||||
new SqlTuple( List.of( (Expression) result, (Expression) offset ), valueMapping ),
|
||||
navigablePath,
|
||||
valueMapping,
|
||||
tableGroup
|
||||
);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ( operator == SUBTRACT ) {
|
||||
|
@ -5260,13 +5336,13 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
// x * (d1 - d2) => x * d1 - x * d2
|
||||
// -x * (d1 + d2) => - x * d1 - x * d2
|
||||
// -x * (d1 - d2) => - x * d1 + x * d2
|
||||
Expression duration = toSqlExpression( expression.getLeftHandOperand().accept( this ) );
|
||||
Expression duration = toSqlExpression( lhs.accept( this ) );
|
||||
Expression scale = adjustmentScale;
|
||||
boolean negate = negativeAdjustment;
|
||||
adjustmentScale = applyScale( duration );
|
||||
negativeAdjustment = false; //was sucked into the scale
|
||||
try {
|
||||
return expression.getRightHandOperand().accept( this );
|
||||
return rhs.accept( this );
|
||||
}
|
||||
finally {
|
||||
adjustmentScale = scale;
|
||||
|
@ -5294,8 +5370,11 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
// must apply the scale, and the 'by unit'
|
||||
// ts1 - ts2
|
||||
|
||||
Expression left = cleanly( () -> toSqlExpression( expression.getLeftHandOperand().accept( this ) ) );
|
||||
Expression right = cleanly( () -> toSqlExpression( expression.getRightHandOperand().accept( this ) ) );
|
||||
final SqmExpression<?> lhs = SqmExpressionHelper.getActualExpression( expression.getLeftHandOperand() );
|
||||
final SqmExpression<?> rhs = SqmExpressionHelper.getActualExpression( expression.getRightHandOperand() );
|
||||
|
||||
Expression left = getActualExpression( cleanly( () -> toSqlExpression( lhs.accept( this ) ) ) );
|
||||
Expression right = getActualExpression( cleanly( () -> toSqlExpression( rhs.accept( this ) ) ) );
|
||||
|
||||
TypeConfiguration typeConfiguration = getCreationContext().getMappingMetamodel().getTypeConfiguration();
|
||||
TemporalType leftTimestamp = typeConfiguration.getSqlTemporalType( expression.getLeftHandOperand().getNodeType() );
|
||||
|
@ -5303,10 +5382,10 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
|
||||
// when we're dealing with Dates, we use
|
||||
// DAY as the smallest unit, otherwise we
|
||||
// use a platform-specific granularity
|
||||
// use SECOND granularity with fractions as that is what the DurationJavaType expects
|
||||
|
||||
TemporalUnit baseUnit = ( rightTimestamp == TemporalType.TIMESTAMP || leftTimestamp == TemporalType.TIMESTAMP ) ?
|
||||
NATIVE :
|
||||
SECOND :
|
||||
DAY;
|
||||
|
||||
if ( adjustedTimestamp != null ) {
|
||||
|
@ -5346,6 +5425,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
}
|
||||
}
|
||||
|
||||
private static Expression getActualExpression(Expression expression) {
|
||||
if ( expression.getExpressionType() instanceof EmbeddableValuedModelPart ) {
|
||||
final EmbeddableValuedModelPart embeddableValuedModelPart = (EmbeddableValuedModelPart) expression.getExpressionType();
|
||||
if ( embeddableValuedModelPart.getJavaType() instanceof TemporalJavaType<?> ) {
|
||||
return ( (SqlTupleContainer) expression ).getSqlTuple().getExpressions().get( 0 );
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
private <J> BasicType<J> basicType(Class<J> javaType) {
|
||||
return creationContext.getMappingMetamodel().getTypeConfiguration().getBasicTypeForJavaType( javaType );
|
||||
}
|
||||
|
|
|
@ -35,10 +35,10 @@ public class EmbeddableValuedPathInterpretation<T> extends AbstractSqmPathInterp
|
|||
/**
|
||||
* Static factory
|
||||
*/
|
||||
public static <T> EmbeddableValuedPathInterpretation<T> from(
|
||||
public static <T> Expression from(
|
||||
SqmEmbeddedValuedSimplePath<T> sqmPath,
|
||||
SqmToSqlAstConverter converter,
|
||||
SemanticQueryWalker sqmWalker,
|
||||
SemanticQueryWalker<?> sqmWalker,
|
||||
boolean jpaQueryComplianceEnabled) {
|
||||
TableGroup tableGroup = converter.getFromClauseAccess().findTableGroup( sqmPath.getLhs().getNavigablePath() );
|
||||
|
||||
|
|
|
@ -9,20 +9,27 @@ package org.hibernate.query.sqm.tree.expression;
|
|||
import java.sql.Date;
|
||||
import java.sql.Time;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
|
||||
import org.hibernate.query.BindableType;
|
||||
import org.hibernate.query.hql.spi.SqmCreationState;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.sqm.BinaryArithmeticOperator;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
import org.hibernate.query.sqm.TemporalUnit;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.type.descriptor.java.JdbcDateJavaType;
|
||||
import org.hibernate.type.descriptor.java.JdbcTimeJavaType;
|
||||
import org.hibernate.type.descriptor.java.JdbcTimestampJavaType;
|
||||
import org.hibernate.type.descriptor.java.TemporalJavaType;
|
||||
import org.hibernate.type.spi.TypeConfiguration;
|
||||
import org.hibernate.usertype.internal.AbstractTimeZoneStorageCompositeUserType;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -100,4 +107,63 @@ public class SqmExpressionHelper {
|
|||
creationState.getCreationContext().getQueryEngine().getCriteriaBuilder()
|
||||
);
|
||||
}
|
||||
|
||||
public static boolean isCompositeTemporal(SqmExpression<?> expression) {
|
||||
// When TimeZoneStorageStrategy.COLUMN is used, that implies using a composite user type
|
||||
return expression instanceof SqmPath<?> && expression.getNodeType() instanceof EmbeddedSqmPathSource<?>
|
||||
&& expression.getJavaTypeDescriptor() instanceof TemporalJavaType<?>;
|
||||
}
|
||||
|
||||
public static SqmExpression<?> getActualExpression(SqmExpression<?> expression) {
|
||||
if ( isCompositeTemporal( expression ) ) {
|
||||
return ( (SqmPath<?>) expression ).get( AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME );
|
||||
}
|
||||
else {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
|
||||
public static SqmExpression<?> getOffsetAdjustedExpression(SqmExpression<?> expression) {
|
||||
if ( isCompositeTemporal( expression ) ) {
|
||||
final SqmPath<?> compositePath = (SqmPath<?>) expression;
|
||||
final SqmPath<Object> instantPath = compositePath.get( AbstractTimeZoneStorageCompositeUserType.INSTANT_NAME );
|
||||
final NodeBuilder nodeBuilder = instantPath.nodeBuilder();
|
||||
return new SqmBinaryArithmetic<>(
|
||||
BinaryArithmeticOperator.ADD,
|
||||
instantPath,
|
||||
new SqmToDuration<>(
|
||||
compositePath.get( AbstractTimeZoneStorageCompositeUserType.ZONE_OFFSET_NAME ),
|
||||
new SqmDurationUnit<>( TemporalUnit.SECOND, nodeBuilder.getIntegerType(), nodeBuilder ),
|
||||
nodeBuilder.getTypeConfiguration().getBasicTypeForJavaType( Duration.class ),
|
||||
nodeBuilder
|
||||
),
|
||||
instantPath.getNodeType(),
|
||||
nodeBuilder
|
||||
);
|
||||
}
|
||||
else {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
|
||||
public static SqmPath<?> findPath(SqmExpression<?> expression, SqmExpressible<?> nodeType) {
|
||||
if ( nodeType != expression.getNodeType() ) {
|
||||
return null;
|
||||
}
|
||||
if ( expression instanceof SqmPath<?> ) {
|
||||
return (SqmPath<?>) expression;
|
||||
}
|
||||
else if ( expression instanceof SqmBinaryArithmetic<?> ) {
|
||||
final SqmBinaryArithmetic<?> binaryArithmetic = (SqmBinaryArithmetic<?>) expression;
|
||||
final SqmPath<?> lhs = findPath( binaryArithmetic.getLeftHandOperand(), nodeType );
|
||||
if ( lhs != null ) {
|
||||
return lhs;
|
||||
}
|
||||
final SqmPath<?> rhs = findPath( binaryArithmetic.getRightHandOperand(), nodeType );
|
||||
if ( rhs != null ) {
|
||||
return rhs;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ import org.hibernate.type.StandardBasicTypes;
|
|||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter;
|
||||
|
||||
import static org.hibernate.query.sqm.TemporalUnit.NANOSECOND;
|
||||
import static org.hibernate.query.sqm.TemporalUnit.SECOND;
|
||||
import static org.hibernate.sql.ast.SqlTreePrinter.logSqlAst;
|
||||
import static org.hibernate.sql.results.graph.DomainResultGraphPrinter.logDomainResultGraph;
|
||||
|
||||
|
@ -3396,6 +3396,10 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
return expression instanceof JdbcParameter || expression instanceof SqmParameterInterpretation;
|
||||
}
|
||||
|
||||
protected final boolean isLiteral(Expression expression) {
|
||||
return expression instanceof Literal;
|
||||
}
|
||||
|
||||
protected List<SortSpecification> getSortSpecificationsRowNumbering(
|
||||
SelectClause selectClause,
|
||||
QueryPart queryPart) {
|
||||
|
@ -4432,7 +4436,7 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
|
|||
public void visitDuration(Duration duration) {
|
||||
duration.getMagnitude().accept( this );
|
||||
appendSql(
|
||||
duration.getUnit().conversionFactor( NANOSECOND, getDialect() )
|
||||
duration.getUnit().conversionFactor( SECOND, getDialect() )
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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.sql.ast.tree.from;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.metamodel.mapping.ModelPartContainer;
|
||||
import org.hibernate.query.spi.NavigablePath;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
public class SyntheticVirtualTableGroup extends AbstractTableGroup implements VirtualTableGroup {
|
||||
private final TableGroup underlyingTableGroup;
|
||||
private final TableReference syntheticTableReference;
|
||||
|
||||
public SyntheticVirtualTableGroup(
|
||||
NavigablePath navigablePath,
|
||||
ModelPartContainer modelPart,
|
||||
TableGroup underlyingTableGroup) {
|
||||
super(
|
||||
underlyingTableGroup.canUseInnerJoins(),
|
||||
navigablePath,
|
||||
modelPart,
|
||||
underlyingTableGroup.getSourceAlias(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
this.underlyingTableGroup = underlyingTableGroup;
|
||||
this.syntheticTableReference = new NamedTableReference(
|
||||
navigablePath.getFullPath(),
|
||||
navigablePath.getLocalName(),
|
||||
false,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelPartContainer getExpressionType() {
|
||||
return getModelPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFetched() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceAlias() {
|
||||
return underlyingTableGroup.getSourceAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseInnerJoins() {
|
||||
return underlyingTableGroup.canUseInnerJoins();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyAffectedTableNames(Consumer<String> nameCollector) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableReference getPrimaryTableReference() {
|
||||
return syntheticTableReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TableReferenceJoin> getTableReferenceJoins() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableReference getTableReferenceInternal(
|
||||
NavigablePath navigablePath,
|
||||
String tableExpression,
|
||||
boolean allowFkOptimization,
|
||||
boolean resolve) {
|
||||
final TableReference tableReference = underlyingTableGroup.getPrimaryTableReference()
|
||||
.getTableReference( navigablePath, tableExpression, allowFkOptimization, resolve );
|
||||
if ( tableReference != null ) {
|
||||
return syntheticTableReference;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,7 +7,10 @@
|
|||
package org.hibernate.type.descriptor.java;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.time.Duration;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
|
@ -37,6 +40,13 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
|||
* Singleton access
|
||||
*/
|
||||
public static final DurationJavaType INSTANCE = new DurationJavaType();
|
||||
private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = DecimalFormatSymbols.getInstance( Locale.ENGLISH );
|
||||
private static final ThreadLocal<DecimalFormat> DECIMAL_FORMAT = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected DecimalFormat initialValue() {
|
||||
return new DecimalFormat( "0.000000000", DECIMAL_FORMAT_SYMBOLS );
|
||||
}
|
||||
};
|
||||
|
||||
public DurationJavaType() {
|
||||
super( Duration.class, ImmutableMutabilityPlan.instance() );
|
||||
|
@ -110,16 +120,11 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
|||
}
|
||||
|
||||
if (value instanceof BigDecimal) {
|
||||
BigDecimal[] secondsAndNanos =
|
||||
((BigDecimal) value).divideAndRemainder( BigDecimal.ONE );
|
||||
return Duration.ofSeconds(
|
||||
secondsAndNanos[0].longValueExact(),
|
||||
// use intValue() not intValueExact() here, because
|
||||
// the database will sometimes produce garbage digits
|
||||
// in a floating point multiplication, and we would
|
||||
// get an unwanted ArithmeticException
|
||||
secondsAndNanos[1].intValue()
|
||||
);
|
||||
return fromDecimal( value );
|
||||
}
|
||||
|
||||
if (value instanceof Double) {
|
||||
return fromDecimal( value );
|
||||
}
|
||||
|
||||
if (value instanceof Long) {
|
||||
|
@ -133,6 +138,18 @@ public class DurationJavaType extends AbstractClassJavaType<Duration> {
|
|||
throw unknownWrap( value.getClass() );
|
||||
}
|
||||
|
||||
private Duration fromDecimal(Object number) {
|
||||
final String formatted = DECIMAL_FORMAT.get().format( number );
|
||||
final int dotIndex = formatted.indexOf( '.' );
|
||||
if (dotIndex == -1) {
|
||||
return Duration.ofSeconds( Long.parseLong( formatted ) );
|
||||
}
|
||||
return Duration.ofSeconds(
|
||||
Long.parseLong( formatted.substring( 0, dotIndex ) ),
|
||||
Long.parseLong( formatted.substring( dotIndex + 1 ) )
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefaultSqlPrecision(Dialect dialect, JdbcType jdbcType) {
|
||||
if ( jdbcType.getDefaultSqlTypeCode() == SqlTypes.INTERVAL_SECOND ) {
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.type.descriptor.java;
|
|||
import java.sql.Timestamp;
|
||||
import java.sql.Types;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
@ -91,6 +92,10 @@ public class InstantJavaType extends AbstractTemporalJavaType<Instant>
|
|||
return (X) instant;
|
||||
}
|
||||
|
||||
if ( OffsetDateTime.class.isAssignableFrom( type ) ) {
|
||||
return (X) instant.atOffset( ZoneOffset.UTC );
|
||||
}
|
||||
|
||||
if ( Calendar.class.isAssignableFrom( type ) ) {
|
||||
return (X) GregorianCalendar.from( instant.atZone( ZoneOffset.UTC ) );
|
||||
}
|
||||
|
@ -144,6 +149,10 @@ public class InstantJavaType extends AbstractTemporalJavaType<Instant>
|
|||
return (Instant) value;
|
||||
}
|
||||
|
||||
if ( value instanceof OffsetDateTime ) {
|
||||
return ( (OffsetDateTime) value ).toInstant();
|
||||
}
|
||||
|
||||
if ( value instanceof Timestamp ) {
|
||||
final Timestamp ts = (Timestamp) value;
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.usertype.internal;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.hibernate.usertype.CompositeUserType;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
public abstract class AbstractTimeZoneStorageCompositeUserType<T> implements CompositeUserType<T> {
|
||||
|
||||
public static final String INSTANT_NAME = "instant";
|
||||
public static final String ZONE_OFFSET_NAME = "zoneOffset";
|
||||
|
||||
@Override
|
||||
public boolean equals(Object x, Object y) {
|
||||
return x.equals( y );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode(Object x) {
|
||||
return x.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deepCopy(Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Serializable disassemble(Object value) {
|
||||
return (Serializable) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object assemble(Serializable cached, Object owner) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object replace(Object detached, Object managed, Object owner) {
|
||||
return detached;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.usertype.internal;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.spi.ValueAccess;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
public class OffsetDateTimeCompositeUserType extends AbstractTimeZoneStorageCompositeUserType<OffsetDateTime> {
|
||||
|
||||
@Override
|
||||
public Object getPropertyValue(OffsetDateTime component, int property) throws HibernateException {
|
||||
switch ( property ) {
|
||||
case 0:
|
||||
return component.toInstant();
|
||||
case 1:
|
||||
return component.getOffset();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OffsetDateTime instantiate(ValueAccess values, SessionFactoryImplementor sessionFactory) {
|
||||
final Instant instant = values.getValue( 0, Instant.class );
|
||||
final ZoneOffset zoneOffset = values.getValue( 1, ZoneOffset.class );
|
||||
return instant == null || zoneOffset == null ? null : OffsetDateTime.ofInstant( instant, zoneOffset );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> embeddable() {
|
||||
return OffsetDateTimeEmbeddable.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<OffsetDateTime> returnedClass() {
|
||||
return OffsetDateTime.class;
|
||||
}
|
||||
|
||||
public static class OffsetDateTimeEmbeddable {
|
||||
private Instant instant;
|
||||
@JdbcTypeCode( SqlTypes.INTEGER )
|
||||
private ZoneOffset zoneOffset;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.usertype.internal;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.metamodel.spi.ValueAccess;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
/**
|
||||
* @author Christian Beikov
|
||||
*/
|
||||
public class ZonedDateTimeCompositeUserType extends AbstractTimeZoneStorageCompositeUserType<ZonedDateTime> {
|
||||
|
||||
@Override
|
||||
public Object getPropertyValue(ZonedDateTime component, int property) throws HibernateException {
|
||||
switch ( property ) {
|
||||
case 0:
|
||||
return component.toInstant();
|
||||
case 1:
|
||||
return component.getOffset();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZonedDateTime instantiate(ValueAccess values, SessionFactoryImplementor sessionFactory) {
|
||||
final Instant instant = values.getValue( 0, Instant.class );
|
||||
final ZoneOffset zoneOffset = values.getValue( 1, ZoneOffset.class );
|
||||
return instant == null || zoneOffset == null ? null : ZonedDateTime.ofInstant( instant, zoneOffset );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> embeddable() {
|
||||
return ZonedDateTimeEmbeddable.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<ZonedDateTime> returnedClass() {
|
||||
return ZonedDateTime.class;
|
||||
}
|
||||
|
||||
public static class ZonedDateTimeEmbeddable {
|
||||
private Instant instant;
|
||||
@JdbcTypeCode( SqlTypes.INTEGER )
|
||||
private ZoneOffset zoneOffset;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue