diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc index d45d5be7ad..e28ec37875 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -800,13 +800,14 @@ include::{example-dir-hql}/HQLTest.java[tags=hql-nullif-example] [[hql-functions-datetime]] ==== Functions for working with dates and times -There are two very important function for working with dates and times. +There are some very important functions for working with dates and times. |=== | Special function | Purpose | Signature | JPA standard | `extract()` | Extract a datetime field | `extract(field from x)` | ✓ | `format()` | Format a datetime as a string | `format(datetime as pattern)` | ✗ +| `trunc()` or `truncate()` | Datetime truncation | `truncate(datetime, field)` | ✗ |=== [[hql-function-extract]] @@ -855,6 +856,16 @@ The syntax is `format(datetime as pattern)`, and the pattern must be written in For a full list of `format()` pattern elements, see the Javadoc for https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/dialect/Dialect.html#appendDatetimeFormat[`Dialect#appendDatetimeFormat`]. +[[hql-function-trunc-datetime]] +===== `trunc()` or `truncate()` + +This function truncates a date, time, or datetime to the temporal unit specified by field. + +The syntax is `truncate(datetime, field)`. Supported temporal units are: `year`, `month`, `day`, `hour`, `minute` or `second`. + +Truncating a date, time or datetime value translates to obtaining a value of the same type in which all temporal units smaller than `field` have been pruned. +For hours, minutes and second this means setting them to `00`. For months and days, this means setting them to `01'. + [[hql-string-functions]] ==== Functions for working with strings diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CacheDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CacheDialect.java index cb4a0244d7..f4d6e8f0cb 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CacheDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CacheDialect.java @@ -168,7 +168,7 @@ public class CacheDialect extends Dialect { functionFactory.weekQuarter(); functionFactory.daynameMonthname(); functionFactory.toCharNumberDateTimestamp(); - functionFactory.truncate(); + functionFactory.trunc_truncate(); functionFactory.dayofweekmonthyear(); functionFactory.repeat_replicate(); functionFactory.datepartDatename(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java index cb7f9a386e..c0e6ba38ed 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java @@ -45,7 +45,7 @@ import org.hibernate.dialect.SpannerDialect; import org.hibernate.dialect.TimeZoneSupport; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.FormatFunction; -import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction; +import org.hibernate.dialect.function.PostgreSQLTruncFunction; import org.hibernate.dialect.identity.CockroachDBIdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; @@ -435,7 +435,11 @@ public class CockroachLegacyDialect extends Dialect { functionFactory.hypotheticalOrderedSetAggregates_windowEmulation(); functionContributions.getFunctionRegistry().register( - "trunc", new PostgreSQLTruncRoundFunction( "trunc", getVersion().isSameOrAfter( 22, 2 ) ) + "trunc", + new PostgreSQLTruncFunction( + getVersion().isSameOrAfter( 22, 2 ), + functionContributions.getTypeConfiguration() + ) ); functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java index 0043eb9d01..e5743e81c1 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/DB2LegacyDialect.java @@ -295,8 +295,6 @@ public class DB2LegacyDialect extends Dialect { functionFactory.octetLength(); functionFactory.ascii(); functionFactory.char_chr(); - functionFactory.trunc(); -// functionFactory.truncate(); functionFactory.insert(); functionFactory.characterLength_length( SqlAstNodeRenderingMode.DEFAULT ); functionFactory.stddev(); @@ -312,6 +310,7 @@ public class DB2LegacyDialect extends Dialect { functionFactory.varPopSamp(); functionFactory.varianceSamp(); functionFactory.dateTrunc(); + functionFactory.trunc_dateTrunc(); } else { // Before version 11, the position function required the use of the code units @@ -327,7 +326,7 @@ public class DB2LegacyDialect extends Dialect { functionFactory.stddevSamp_sumCount(); functionContributions.getFunctionRegistry().registerAlternateKey( "var_pop", "variance" ); functionFactory.varSamp_sumCount(); - functionFactory.dateTrunc_trunc(); + functionFactory.trunc_dateTrunc_trunc(); } functionFactory.addYearsMonthsDaysHoursMinutesSeconds(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java index 8f2f044033..9c46b0f7d8 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java @@ -309,8 +309,7 @@ public class H2LegacyDialect extends Dialect { if ( useLocalTime ) { functionFactory.localtimeLocaltimestamp(); } - functionFactory.trunc(); -// functionFactory.truncate(); + functionFactory.trunc_dateTrunc(); functionFactory.dateTrunc(); functionFactory.bitLength(); functionFactory.octetLength(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java index b7c14573a8..ad9d0ba407 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java @@ -199,9 +199,7 @@ public class HSQLLegacyDialect extends Dialect { functionFactory.degrees(); functionFactory.log10(); functionFactory.rand(); - functionFactory.trunc(); -// functionFactory.truncate(); - functionFactory.dateTrunc_trunc(); + functionFactory.trunc_dateTrunc_trunc(); functionFactory.pi(); functionFactory.soundex(); functionFactory.reverse(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java index a8a9a9d342..3648387642 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/IngresDialect.java @@ -251,8 +251,8 @@ public class IngresDialect extends Dialect { functionFactory.octetLength(); functionFactory.repeat(); functionFactory.trim2(); - functionFactory.trunc(); -// functionFactory.truncate(); + functionFactory.dateTrunc(); + functionFactory.trunc_dateTrunc(); functionFactory.initcap(); functionFactory.yearMonthDay(); functionFactory.hourMinuteSecond(); @@ -269,7 +269,6 @@ public class IngresDialect extends Dialect { functionFactory.sysdate(); functionFactory.position(); functionFactory.format_dateFormat(); - functionFactory.dateTrunc(); functionFactory.bitLength_pattern( "octet_length(hex(?1))*4" ); final BasicType integerType = functionContributions.getTypeConfiguration().getBasicTypeRegistry() diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MimerSQLDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MimerSQLDialect.java index a24c3644cc..49238051e9 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MimerSQLDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MimerSQLDialect.java @@ -158,7 +158,7 @@ public class MimerSQLDialect extends Dialect { functionFactory.soundex(); functionFactory.octetLength(); functionFactory.bitLength(); - functionFactory.truncate(); + functionFactory.trunc_truncate(); functionFactory.repeat(); functionFactory.pad_repeat(); functionFactory.dayofweekmonthyear(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java index 8eb3d124c1..183c02dd3a 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java @@ -562,7 +562,6 @@ public class MySQLLegacyDialect extends Dialect { functionFactory.position(); functionFactory.nowCurdateCurtime(); functionFactory.trunc_truncate(); - functionFactory.dateTrunc_format( "str_to_date", false ); functionFactory.insert(); functionFactory.bitandorxornot_operator(); functionFactory.bitAndOr(); diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java index 08df7598c2..6f64cb7350 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacyDialect.java @@ -38,6 +38,7 @@ import org.hibernate.dialect.aggregate.OracleAggregateSupport; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.ModeStatsModeEmulation; import org.hibernate.dialect.function.NvlCoalesceEmulation; +import org.hibernate.dialect.function.OracleTruncFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.Oracle12cIdentityColumnSupport; import org.hibernate.dialect.pagination.LegacyOracleLimitHandler; @@ -185,7 +186,6 @@ public class OracleLegacyDialect extends Dialect { functionFactory.cosh(); functionFactory.sinh(); functionFactory.tanh(); - functionFactory.trunc(); functionFactory.log(); functionFactory.log10_log(); functionFactory.soundex(); @@ -255,6 +255,11 @@ public class OracleLegacyDialect extends Dialect { "mode", new ModeStatsModeEmulation( typeConfiguration ) ); + functionContributions.getFunctionRegistry().register( + "trunc", + new OracleTruncFunction( functionContributions.getTypeConfiguration() ) + ); + functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); } @Override diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java index ad6893077c..7b7d330328 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java @@ -49,6 +49,7 @@ import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.dialect.aggregate.PostgreSQLAggregateSupport; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.PostgreSQLMinMaxFunction; +import org.hibernate.dialect.function.PostgreSQLTruncFunction; import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport; @@ -541,8 +542,6 @@ public class PostgreSQLLegacyDialect extends Dialect { CommonFunctionFactory functionFactory = new CommonFunctionFactory(functionContributions); - functionFactory.round_roundFloor(); //Postgres round(x,n) does not accept double - functionFactory.trunc_truncFloor(); functionFactory.cot(); functionFactory.radians(); functionFactory.degrees(); @@ -571,7 +570,6 @@ public class PostgreSQLLegacyDialect extends Dialect { functionFactory.toCharNumberDateTimestamp(); functionFactory.concat_pipeOperator( "convert_from(lo_get(?1),pg_client_encoding())" ); functionFactory.localtimeLocaltimestamp(); - functionFactory.dateTrunc(); functionFactory.length_characterLength_pattern( "length(lo_get(?1),pg_client_encoding())" ); functionFactory.bitLength_pattern( "bit_length(?1)", "length(lo_get(?1))*8" ); functionFactory.octetLength_pattern( "octet_length(?1)", "length(lo_get(?1))" ); @@ -613,9 +611,11 @@ public class PostgreSQLLegacyDialect extends Dialect { "round", new PostgreSQLTruncRoundFunction( "round", true ) ); functionContributions.getFunctionRegistry().register( - "trunc", new PostgreSQLTruncRoundFunction( "trunc", true ) + "trunc", + new PostgreSQLTruncFunction( true, functionContributions.getTypeConfiguration() ) ); functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); + functionFactory.dateTrunc(); } /** diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java index ab295fb4a5..bd506fef0b 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SQLServerLegacyDialect.java @@ -21,6 +21,7 @@ import org.hibernate.dialect.TimeZoneSupport; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.CountFunction; import org.hibernate.dialect.function.SQLServerFormatEmulation; +import org.hibernate.dialect.function.SqlServerConvertTruncFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.SQLServerIdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; @@ -303,7 +304,6 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect { functionFactory.log_log(); - functionFactory.trunc_round(); functionFactory.round_round(); functionFactory.everyAny_minMaxIif(); functionFactory.octetLength_pattern( "datalength(?1)" ); @@ -367,9 +367,14 @@ public class SQLServerLegacyDialect extends AbstractTransactSQLDialect { if ( getVersion().isSameOrAfter( 16 ) ) { functionFactory.leastGreatest(); functionFactory.dateTrunc_datetrunc(); + functionFactory.trunc_round_datetrunc(); } else { - functionFactory.dateTrunc_format( "convert", false ); + functionContributions.getFunctionRegistry().register( + "trunc", + new SqlServerConvertTruncFunction( functionContributions.getTypeConfiguration() ) + ); + functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); } } diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java index 6e14d34cc6..fc8b471567 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/SybaseLegacyDialect.java @@ -18,6 +18,7 @@ import org.hibernate.dialect.NationalizationSupport; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.CountFunction; import org.hibernate.dialect.function.IntegralTimestampaddFunction; +import org.hibernate.dialect.function.SybaseTruncFunction; import org.hibernate.dialect.unique.SkipNullableUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; @@ -216,9 +217,7 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect { functionFactory.varPopSamp_varp(); functionFactory.stddevPopSamp(); functionFactory.varPopSamp(); - functionFactory.trunc_floorPower(); functionFactory.round_round(); - functionFactory.dateTrunc_format( "convert", true ); // For SQL-Server we need to cast certain arguments to varchar(16384) to be able to concat them functionContributions.getFunctionRegistry().register( @@ -249,6 +248,11 @@ public class SybaseLegacyDialect extends AbstractTransactSQLDialect { functionContributions.getFunctionRegistry().register( "timestampadd", new IntegralTimestampaddFunction( this, functionContributions.getTypeConfiguration() ) ); + functionContributions.getFunctionRegistry().register( + "trunc", + new SybaseTruncFunction( functionContributions.getTypeConfiguration() ) + ); + functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); } @Override diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 index 40d2322203..0c0203728f 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 @@ -290,6 +290,7 @@ TO : [tT] [oO]; TRAILING : [tT] [rR] [aA] [iI] [lL] [iI] [nN] [gG]; TREAT : [tT] [rR] [eE] [aA] [tT]; TRIM : [tT] [rR] [iI] [mM]; +TRUNC : [tT] [rR] [uU] [nN] [cC]; TRUNCATE : [tT] [rR] [uU] [nN] [cC] [aA] [tT] [eE]; TYPE : [tT] [yY] [pP] [eE]; UNBOUNDED : [uU] [nN] [bB] [oO] [uU] [nN] [dD] [eE] [dD]; diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 index 8628acef17..191a438380 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlParser.g4 @@ -1245,6 +1245,7 @@ frameExclusion standardFunction : castFunction | extractFunction + | truncFunction | formatFunction | collateFunction | substringFunction @@ -1452,6 +1453,13 @@ extractFunction | datetimeField LEFT_PAREN expression RIGHT_PAREN ; +/** + * The 'trunc()' function for truncating both numeric and datetime values + */ +truncFunction + : (TRUNC | TRUNCATE) LEFT_PAREN expression (COMMA (datetimeField | expression))? RIGHT_PAREN + ; + /** * A field that may be extracted from a date, time, or datetime */ @@ -1688,6 +1696,7 @@ rollup | TRAILING | TREAT | TRIM + | TRUNC | TRUNCATE | TYPE | UNBOUNDED diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java index 84e0b589ef..eebb2dada7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -398,7 +398,6 @@ public abstract class AbstractHANADialect extends Dialect { functionFactory.sinh(); functionFactory.tanh(); functionFactory.trunc_roundMode(); - functionFactory.dateTrunc_format( "to_date", false ); functionFactory.log10_log(); functionFactory.log(); functionFactory.bitand(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java index bff9f06d5e..a119f539ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -27,7 +27,7 @@ import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.FormatFunction; -import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction; +import org.hibernate.dialect.function.PostgreSQLTruncFunction; import org.hibernate.dialect.identity.CockroachDBIdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; @@ -430,7 +430,11 @@ public class CockroachDialect extends Dialect { functionFactory.hypotheticalOrderedSetAggregates_windowEmulation(); functionContributions.getFunctionRegistry().register( - "trunc", new PostgreSQLTruncRoundFunction( "trunc", getVersion().isSameOrAfter( 22, 2 ) ) + "trunc", + new PostgreSQLTruncFunction( + getVersion().isSameOrAfter( 22, 2 ), + functionContributions.getTypeConfiguration() + ) ); functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index e1b5a2b4ea..eb303e3def 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -289,8 +289,6 @@ public class DB2Dialect extends Dialect { functionFactory.octetLength(); functionFactory.ascii(); functionFactory.char_chr(); - functionFactory.trunc(); -// functionFactory.truncate(); functionFactory.insert(); functionFactory.characterLength_length( SqlAstNodeRenderingMode.DEFAULT ); functionFactory.stddev(); @@ -306,6 +304,7 @@ public class DB2Dialect extends Dialect { functionFactory.varPopSamp(); functionFactory.varianceSamp(); functionFactory.dateTrunc(); + functionFactory.trunc_dateTrunc(); } else { // Before version 11, the position function required the use of the code units @@ -321,7 +320,7 @@ public class DB2Dialect extends Dialect { functionFactory.stddevSamp_sumCount(); functionContributions.getFunctionRegistry().registerAlternateKey( "var_pop", "variance" ); functionFactory.varSamp_sumCount(); - functionFactory.dateTrunc_trunc(); + functionFactory.trunc_dateTrunc_trunc(); } functionFactory.addYearsMonthsDaysHoursMinutesSeconds(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index 82627e12c5..56b8add4c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -286,8 +286,7 @@ public class H2Dialect extends Dialect { if ( useLocalTime ) { functionFactory.localtimeLocaltimestamp(); } - functionFactory.trunc(); -// functionFactory.truncate(); + functionFactory.trunc_dateTrunc(); functionFactory.dateTrunc(); functionFactory.bitLength(); functionFactory.octetLength(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index fbf8480f20..72cdfbe3ca 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -140,9 +140,7 @@ public class HSQLDialect extends Dialect { functionFactory.degrees(); functionFactory.log10(); functionFactory.rand(); - functionFactory.trunc(); -// functionFactory.truncate(); - functionFactory.dateTrunc_trunc(); + functionFactory.trunc_dateTrunc_trunc(); functionFactory.pi(); functionFactory.soundex(); functionFactory.reverse(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index 927b4fc537..dd7c94c384 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -561,7 +561,6 @@ public class MySQLDialect extends Dialect { functionFactory.position(); functionFactory.nowCurdateCurtime(); functionFactory.trunc_truncate(); - functionFactory.dateTrunc_format( "str_to_date", false ); functionFactory.insert(); functionFactory.bitandorxornot_operator(); functionFactory.bitAndOr(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 5b5aa96f1f..5acd47acb7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -24,6 +24,7 @@ import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.dialect.aggregate.OracleAggregateSupport; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.ModeStatsModeEmulation; +import org.hibernate.dialect.function.OracleTruncFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.Oracle12cIdentityColumnSupport; import org.hibernate.dialect.pagination.LegacyOracleLimitHandler; @@ -222,7 +223,6 @@ public class OracleDialect extends Dialect { functionFactory.cosh(); functionFactory.sinh(); functionFactory.tanh(); - functionFactory.trunc(); functionFactory.log(); functionFactory.log10_log(); functionFactory.soundex(); @@ -236,7 +236,6 @@ public class OracleDialect extends Dialect { functionFactory.bitand(); functionFactory.lastDay(); functionFactory.toCharNumberDateTimestamp(); - functionFactory.dateTrunc_trunc(); functionFactory.ceiling_ceil(); functionFactory.concat_pipeOperator(); functionFactory.rownumRowid(); @@ -288,6 +287,11 @@ public class OracleDialect extends Dialect { "mode", new ModeStatsModeEmulation( typeConfiguration ) ); + functionContributions.getFunctionRegistry().register( + "trunc", + new OracleTruncFunction( functionContributions.getTypeConfiguration() ) + ); + functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 84ebef4ab4..396ad37321 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -29,6 +29,7 @@ import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.dialect.aggregate.PostgreSQLAggregateSupport; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.PostgreSQLMinMaxFunction; +import org.hibernate.dialect.function.PostgreSQLTruncFunction; import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport; @@ -564,7 +565,6 @@ public class PostgreSQLDialect extends Dialect { functionFactory.toCharNumberDateTimestamp(); functionFactory.concat_pipeOperator( "convert_from(lo_get(?1),pg_client_encoding())" ); functionFactory.localtimeLocaltimestamp(); - functionFactory.dateTrunc(); functionFactory.length_characterLength_pattern( "length(lo_get(?1),pg_client_encoding())" ); functionFactory.bitLength_pattern( "bit_length(?1)", "length(lo_get(?1))*8" ); functionFactory.octetLength_pattern( "octet_length(?1)", "length(lo_get(?1))" ); @@ -604,9 +604,11 @@ public class PostgreSQLDialect extends Dialect { "round", new PostgreSQLTruncRoundFunction( "round", true ) ); functionContributions.getFunctionRegistry().register( - "trunc", new PostgreSQLTruncRoundFunction( "trunc", true ) + "trunc", + new PostgreSQLTruncFunction( true, functionContributions.getTypeConfiguration() ) ); functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); + functionFactory.dateTrunc(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java index a8972e4a26..8cc39286f2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -28,6 +28,7 @@ import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.CountFunction; import org.hibernate.dialect.function.SQLServerFormatEmulation; +import org.hibernate.dialect.function.SqlServerConvertTruncFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.SQLServerIdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; @@ -309,7 +310,6 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { functionFactory.log_log(); - functionFactory.trunc_round(); functionFactory.round_round(); functionFactory.everyAny_minMaxIif(); functionFactory.octetLength_pattern( "datalength(?1)" ); @@ -371,9 +371,14 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { if ( getVersion().isSameOrAfter( 16 ) ) { functionFactory.leastGreatest(); functionFactory.dateTrunc_datetrunc(); + functionFactory.trunc_round_datetrunc(); } else { - functionFactory.dateTrunc_format( "convert", false ); + functionContributions.getFunctionRegistry().register( + "trunc", + new SqlServerConvertTruncFunction( functionContributions.getTypeConfiguration() ) + ); + functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index ec610bdfb4..d7e299302a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -15,6 +15,7 @@ import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.CountFunction; import org.hibernate.dialect.function.IntegralTimestampaddFunction; +import org.hibernate.dialect.function.SybaseTruncFunction; import org.hibernate.dialect.unique.SkipNullableUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; @@ -220,9 +221,7 @@ public class SybaseDialect extends AbstractTransactSQLDialect { functionFactory.varPopSamp_varp(); functionFactory.stddevPopSamp(); functionFactory.varPopSamp(); - functionFactory.trunc_floorPower(); functionFactory.round_round(); - functionFactory.dateTrunc_format( "convert", true ); // For SQL-Server we need to cast certain arguments to varchar(16384) to be able to concat them functionContributions.getFunctionRegistry().register( @@ -253,6 +252,11 @@ public class SybaseDialect extends AbstractTransactSQLDialect { functionContributions.getFunctionRegistry().register( "timestampadd", new IntegralTimestampaddFunction( this, functionContributions.getTypeConfiguration() ) ); + functionContributions.getFunctionRegistry().register( + "trunc", + new SybaseTruncFunction( functionContributions.getTypeConfiguration() ) + ); + functionContributions.getFunctionRegistry().registerAlternateKey( "truncate", "trunc" ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java index e67d2b59fa..a9815814c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CommonFunctionFactory.java @@ -259,123 +259,67 @@ public class CommonFunctionFactory { } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // basic math functions + // numeric and datetime truncation + + private void trunc( + String truncPattern, + String twoArgTruncPattern, + TruncFunction.DatetimeTrunc datetimeTrunc, + String toDateFunction) { + functionRegistry.register( + "trunc", + new TruncFunction( truncPattern, twoArgTruncPattern, datetimeTrunc, toDateFunction, typeConfiguration ) + ); + functionRegistry.registerAlternateKey( "truncate", "trunc" ); + } + + private void trunc(TruncFunction.DatetimeTrunc datetimeTrunc) { + trunc( "trunc(?1)", "trunc(?1,?2)", datetimeTrunc, null ); + } public void trunc() { - functionRegistry.namedDescriptorBuilder( "trunc" ) - .setReturnTypeResolver( useArgType( 1 ) ) - .setArgumentCountBetween( 1, 2 ) - .setParameterTypes(NUMERIC, INTEGER) - .setArgumentListSignature( "(NUMERIC number[, INTEGER places])" ) - .register(); - functionRegistry.registerAlternateKey( "truncate", "trunc" ); + trunc( null ); + } + + public void trunc_dateTrunc() { + trunc( TruncFunction.DatetimeTrunc.DATE_TRUNC ); + } + + public void trunc_dateTrunc_trunc() { + trunc( TruncFunction.DatetimeTrunc.TRUNC ); } /** * MySQL */ public void trunc_truncate() { - functionRegistry.registerUnaryBinaryPattern( - "trunc", - "truncate(?1,0)", - "truncate(?1,?2)", - NUMERIC, INTEGER, - typeConfiguration - ).setArgumentListSignature( "(NUMERIC number[, INTEGER places])" ); - functionRegistry.registerAlternateKey( "truncate", "trunc" ); + trunc( "truncate(?1,0)", "truncate(?1,?2)", TruncFunction.DatetimeTrunc.FORMAT, "str_to_date" ); } /** - * SQL Server + * SQL Server >= 16 */ - public void trunc_round() { - functionRegistry.registerUnaryBinaryPattern( - "trunc", - "round(?1,0,1)", - "round(?1,?2,1)", - NUMERIC, INTEGER, - typeConfiguration - ).setArgumentListSignature( "(NUMERIC number[, INTEGER places])" ); - functionRegistry.registerAlternateKey( "truncate", "trunc" ); - } - - /** - * Sybase - */ - public void trunc_floorPower() { - functionRegistry.registerUnaryBinaryPattern( - "trunc", - "sign(?1)*floor(abs(?1))", - "sign(?1)*floor(abs(?1)*power(10,?2))/power(10,?2)", - NUMERIC, INTEGER, - typeConfiguration - ).setArgumentListSignature( "(NUMERIC number[, INTEGER places])" ); - functionRegistry.registerAlternateKey( "truncate", "trunc" ); - } - - /** - * PostgreSQL (only works if the second arg is constant, as it almost always is) - */ - public void trunc_truncFloor() { - functionRegistry.registerUnaryBinaryPattern( - "trunc", - "trunc(?1)", - "sign(?1)*floor(abs(?1)*1e?2)/1e?2", - NUMERIC, INTEGER, - typeConfiguration - ).setArgumentListSignature( "(NUMERIC number[, INTEGER places])" ); - functionRegistry.registerAlternateKey( "truncate", "trunc" ); + public void trunc_round_datetrunc() { + trunc( "round(?1,0,1)", "round(?1,?2,1)", TruncFunction.DatetimeTrunc.DATETRUNC, "convert" ); } /** * Derby (only works if the second arg is constant, as it almost always is) */ public void trunc_floor() { - functionRegistry.registerUnaryBinaryPattern( - "trunc", - "sign(?1)*floor(abs(?1))", - "sign(?1)*floor(abs(?1)*1e?2)/1e?2", - NUMERIC, INTEGER, - typeConfiguration - ).setArgumentListSignature( "(NUMERIC number[, INTEGER places])" ); - functionRegistry.registerAlternateKey( "truncate", "trunc" ); - } - - public void truncate() { - functionRegistry.namedDescriptorBuilder( "truncate" ) - .setExactArgumentCount( 2 ) //some databases allow 1 arg but in these it's a synonym for trunc() - .setParameterTypes(NUMERIC, INTEGER) - .setInvariantType(doubleType) - .setArgumentListSignature( "(NUMERIC number, INTEGER places)" ) - .register(); - } - - /** - * SQL Server - */ - public void truncate_round() { - functionRegistry.patternDescriptorBuilder( "truncate", "round(?1,?2,1)" ) - .setExactArgumentCount( 2 ) - .setParameterTypes(NUMERIC, INTEGER) - .setInvariantType(doubleType) - .setArgumentListSignature( "(NUMERIC number, INTEGER places)" ) - .register(); + trunc( "sign(?1)*floor(abs(?1))", "sign(?1)*floor(abs(?1)*1e?2)/1e?2", null, null ); } /** * SAP HANA */ public void trunc_roundMode() { - functionRegistry.registerUnaryBinaryPattern( - "trunc", - "round(?1,0,round_down)", - "round(?1,?2,round_down)", - NUMERIC, INTEGER, - typeConfiguration - ).setArgumentListSignature( "(NUMERIC number[, INTEGER places])" ); - functionRegistry.registerAlternateKey( "truncate", "trunc" ); + trunc( "round(?1,0,round_down)", "round(?1,?2,round_down)", TruncFunction.DatetimeTrunc.FORMAT, "to_date" ); } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // basic math functions + /** * Returns double between 0.0 and 1.0. First call may specify a seed value. */ @@ -2268,7 +2212,6 @@ public class CommonFunctionFactory { NUMERIC, INTEGER, typeConfiguration ).setArgumentListSignature( "(NUMERIC number[, INTEGER places])" ); - functionRegistry.registerAlternateKey( "truncate", "trunc" ); } /** @@ -2558,32 +2501,27 @@ public class CommonFunctionFactory { .register(); } + /** + * H2, DB2 and PostgreSQL native date_trunc() function + */ public void dateTrunc() { - functionRegistry.patternDescriptorBuilder( "date_trunc", "date_trunc('?1',?2)" ) + functionRegistry.patternDescriptorBuilder( "date_trunc", "date_trunc(?1,?2)" ) .setReturnTypeResolver( useArgType( 2 ) ) .setExactArgumentCount( 2 ) - .setParameterTypes( TEMPORAL_UNIT, TEMPORAL ) - .setArgumentListSignature( "(TEMPORAL_UNIT field, TEMPORAL datetime)" ) + .setParameterTypes( STRING, TEMPORAL ) + .setArgumentListSignature( "(STRING field, TEMPORAL datetime)" ) .register(); } + /** + * SQLServer native datetrunc() function + */ public void dateTrunc_datetrunc() { - functionRegistry.patternDescriptorBuilder( "date_trunc", "datetrunc(?1,?2)" ) + functionRegistry.patternDescriptorBuilder( "datetrunc", "datetrunc(?1,?2)" ) .setReturnTypeResolver( useArgType( 2 ) ) .setExactArgumentCount( 2 ) .setParameterTypes( TEMPORAL_UNIT, TEMPORAL ) .setArgumentListSignature( "(TEMPORAL_UNIT field, TEMPORAL datetime)" ) .register(); } - - public void dateTrunc_trunc() { - functionRegistry.register( "date_trunc", new DateTruncTrunc( typeConfiguration ) ); - } - - public void dateTrunc_format(String toDateFunction, boolean useConvertToFormat) { - functionRegistry.register( - "date_trunc", - new DateTruncEmulation( toDateFunction, useConvertToFormat, typeConfiguration ) - ); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/DateTruncEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/DateTruncEmulation.java index e26c2c051e..07382aa7e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/DateTruncEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/DateTruncEmulation.java @@ -6,7 +6,6 @@ */ package org.hibernate.dialect.function; -import java.util.ArrayList; import java.util.List; import org.hibernate.query.ReturnableType; @@ -21,8 +20,8 @@ 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.tree.SqmTypedNode; -import org.hibernate.query.sqm.tree.expression.SqmDurationUnit; import org.hibernate.query.sqm.tree.expression.SqmExpression; +import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; import org.hibernate.query.sqm.tree.expression.SqmFormat; import org.hibernate.query.sqm.tree.expression.SqmLiteral; import org.hibernate.sql.ast.SqlAstTranslator; @@ -35,25 +34,24 @@ import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEM import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL_UNIT; /** - * Emulation of {@code datetrunc} function that leverages + * Emulation of {@code trunc(datetime, temporal_unit)} function that leverages * formatting the datetime to string and back to truncate it * * @author Marco Belladelli */ public class DateTruncEmulation extends AbstractSqmFunctionDescriptor implements FunctionRenderingSupport { - private final String toDateFunction; + protected final String toDateFunction; + private final SqmFormat sqmFormat; - private final boolean useConvertToFormat; - - public DateTruncEmulation(String toDateFunction, boolean useConvertToFormat, TypeConfiguration typeConfiguration) { + protected DateTruncEmulation(String toDateFunction, TypeConfiguration typeConfiguration) { super( - "date_trunc", - new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL_UNIT, TEMPORAL ), - StandardFunctionReturnTypeResolvers.useArgType( 2 ), - StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TEMPORAL_UNIT, TEMPORAL ) + "trunc", + new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL, TEMPORAL_UNIT ), + StandardFunctionReturnTypeResolvers.useArgType( 1 ), + StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TEMPORAL, TEMPORAL_UNIT ) ); this.toDateFunction = toDateFunction; - this.useConvertToFormat = useConvertToFormat; + this.sqmFormat = new SqmFormat( "yyyy-MM-dd HH:mm:ss", typeConfiguration.getBasicTypeForJavaType( String.class ), null ); } @Override @@ -63,28 +61,9 @@ public class DateTruncEmulation extends AbstractSqmFunctionDescriptor implements SqlAstTranslator walker) { sqlAppender.appendSql( toDateFunction ); sqlAppender.append( '(' ); - if ( !useConvertToFormat ) { - if ( toDateFunction.equalsIgnoreCase( "convert" ) ) { - sqlAppender.append( "datetime," ); - sqlAstArguments.get( 0 ).accept( walker ); - } - else { - sqlAstArguments.get( 0 ).accept( walker ); - sqlAppender.append( ',' ); - sqlAstArguments.get( 1 ).accept( walker ); - } - } - else { - // custom implementation that uses convert instead of format for Sybase - // see: https://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc36271.1600/doc/html/san1393050437990.html - sqlAppender.append( "datetime,substring(convert(varchar," ); - sqlAstArguments.get( 1 ).accept( walker ); - sqlAppender.append( ",21),1,17-len(" ); - sqlAstArguments.get( 0 ).accept( walker ); - sqlAppender.append( "))+" ); - sqlAstArguments.get( 0 ).accept( walker ); - sqlAppender.append( ",21" ); - } + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( ',' ); + sqlAstArguments.get( 2 ).accept( walker ); sqlAppender.append( ')' ); } @@ -95,7 +74,7 @@ public class DateTruncEmulation extends AbstractSqmFunctionDescriptor implements QueryEngine queryEngine, TypeConfiguration typeConfiguration) { final NodeBuilder nodeBuilder = queryEngine.getCriteriaBuilder(); - final TemporalUnit temporalUnit = ( (SqmDurationUnit) arguments.get( 0 ) ).getUnit(); + final TemporalUnit temporalUnit = ( (SqmExtractUnit) arguments.get( 1 ) ).getUnit(); final String pattern; final String literal; switch ( temporalUnit ) { @@ -126,18 +105,31 @@ public class DateTruncEmulation extends AbstractSqmFunctionDescriptor implements default: throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" ); } - - final SqmTypedNode datetime = arguments.get( 1 ); - final List> args = new ArrayList<>( 2 ); - if ( !useConvertToFormat ) { - // use standard format function - final SqmExpression formatExpression = queryEngine.getSqmFunctionRegistry() - .findFunctionDescriptor( "format" ) + final SqmTypedNode datetime = arguments.get( 0 ); + final SqmExpression formatExpression = queryEngine.getSqmFunctionRegistry() + .findFunctionDescriptor( "format" ) + .generateSqmExpression( + asList( + datetime, + new SqmFormat( + pattern, + typeConfiguration.getBasicTypeForJavaType( String.class ), + nodeBuilder + ) + ), + null, + queryEngine, + typeConfiguration + ); + final SqmExpression formattedDatetime; + if ( literal != null ) { + formattedDatetime = queryEngine.getSqmFunctionRegistry() + .findFunctionDescriptor( "concat" ) .generateSqmExpression( asList( - datetime, - new SqmFormat( - pattern, + formatExpression, + new SqmLiteral<>( + literal, typeConfiguration.getBasicTypeForJavaType( String.class ), nodeBuilder ) @@ -146,51 +138,21 @@ public class DateTruncEmulation extends AbstractSqmFunctionDescriptor implements queryEngine, typeConfiguration ); - final SqmExpression formattedDatetime; - if ( literal != null ) { - formattedDatetime = queryEngine.getSqmFunctionRegistry() - .findFunctionDescriptor( "concat" ) - .generateSqmExpression( - asList( - formatExpression, - new SqmLiteral<>( - literal, - typeConfiguration.getBasicTypeForJavaType( String.class ), - nodeBuilder - ) - ), - null, - queryEngine, - typeConfiguration - ); - } - else { - formattedDatetime = formatExpression; - } - args.add( formattedDatetime ); - args.add( new SqmFormat( - "yyyy-MM-dd HH:mm:ss", - typeConfiguration.getBasicTypeForJavaType( String.class ), - nodeBuilder - ) ); } else { - args.add( new SqmLiteral<>( - literal != null ? literal.replace( "-", "/" ) : "", - typeConfiguration.getBasicTypeForJavaType( String.class ), - nodeBuilder - ) ); - args.add( datetime ); + formattedDatetime = formatExpression; } + return new SelfRenderingSqmFunction<>( this, this, - args, + // the first argument is needed for SybaseDateTruncEmulation + asList( datetime, formattedDatetime, sqmFormat ), impliedResultType, - getArgumentsValidator(), + null, getReturnTypeResolver(), nodeBuilder, - "date_trunc" + getName() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/DateTruncTrunc.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/DateTruncTrunc.java deleted file mode 100644 index 0688d64b93..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/DateTruncTrunc.java +++ /dev/null @@ -1,127 +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.dialect.function; - -import java.util.ArrayList; -import java.util.List; - -import org.hibernate.dialect.OracleDialect; -import org.hibernate.query.ReturnableType; -import org.hibernate.query.spi.QueryEngine; -import org.hibernate.query.sqm.NodeBuilder; -import org.hibernate.query.sqm.TemporalUnit; -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.tree.SqmTypedNode; -import org.hibernate.query.sqm.tree.expression.SqmDurationUnit; -import org.hibernate.query.sqm.tree.expression.SqmLiteral; -import org.hibernate.sql.ast.SqlAstTranslator; -import org.hibernate.sql.ast.spi.SqlAppender; -import org.hibernate.sql.ast.tree.SqlAstNode; -import org.hibernate.type.spi.TypeConfiguration; - -import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL; -import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL_UNIT; - -/** - * Trunc function which converts the {@link TemporalUnit} - * to the format required for {@code trunc()} - * - * @author Marco Belladelli - */ -public class DateTruncTrunc extends AbstractSqmFunctionDescriptor implements FunctionRenderingSupport { - - public DateTruncTrunc(TypeConfiguration typeConfiguration) { - super( - "date_trunc", - new ArgumentTypesValidator( StandardArgumentsValidators.exactly( 2 ), TEMPORAL_UNIT, TEMPORAL ), - StandardFunctionReturnTypeResolvers.useArgType( 2 ), - StandardFunctionArgumentTypeResolvers.invariant( typeConfiguration, TEMPORAL_UNIT, TEMPORAL ) - ); - } - - @Override - public void render( - SqlAppender sqlAppender, - List sqlAstArguments, - SqlAstTranslator walker) { - sqlAppender.appendSql( "trunc(" ); - sqlAstArguments.get( 1 ).accept( walker ); - sqlAppender.append( ',' ); - sqlAstArguments.get( 0 ).accept( walker ); - sqlAppender.append( ')' ); - } - - @Override - protected SelfRenderingSqmFunction generateSqmFunctionExpression( - List> arguments, - ReturnableType impliedResultType, - QueryEngine queryEngine, - TypeConfiguration typeConfiguration) { - final NodeBuilder nodeBuilder = queryEngine.getCriteriaBuilder(); - final TemporalUnit temporalUnit = ( (SqmDurationUnit) arguments.get( 0 ) ).getUnit(); - final String pattern; - switch ( temporalUnit ) { - case YEAR: - pattern = "YYYY"; - break; - case MONTH: - pattern = "MM"; - break; - case WEEK: - pattern = "IW"; - break; - case DAY: - pattern = "DD"; - break; - case HOUR: - pattern = "HH"; - break; - case MINUTE: - pattern = "MI"; - break; - case SECOND: - if ( nodeBuilder.getSessionFactory().getJdbcServices().getDialect() instanceof OracleDialect ) { - // Oracle does not support truncating to seconds with the native function, use emulation - return new DateTruncEmulation( "to_date", false, typeConfiguration ) - .generateSqmFunctionExpression( - arguments, - impliedResultType, - queryEngine, - typeConfiguration - ); - } - pattern = "SS"; - break; - default: - throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" ); - } - - final List> args = new ArrayList<>( 2 ); - args.add( new SqmLiteral<>( - pattern, - typeConfiguration.getBasicTypeForJavaType( String.class ), - nodeBuilder - ) ); - args.add( arguments.get( 1 ) ); - return new SelfRenderingSqmFunction<>( - this, - this, - args, - impliedResultType, - getArgumentsValidator(), - getReturnTypeResolver(), - nodeBuilder, - "date_trunc" - ); - } -} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/OracleTruncFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/OracleTruncFunction.java new file mode 100644 index 0000000000..14550f80c1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/OracleTruncFunction.java @@ -0,0 +1,114 @@ +/* + * 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.query.ReturnableType; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.TemporalUnit; +import org.hibernate.query.sqm.function.FunctionRenderingSupport; +import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; +import org.hibernate.query.sqm.produce.function.ArgumentsValidator; +import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; +import org.hibernate.query.sqm.tree.expression.SqmLiteral; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Custom {@link TruncFunction} for Oracle which uses emulation when truncating datetimes to seconds + * + * @author Marco Belladelli + */ +public class OracleTruncFunction extends TruncFunction { + private final DateTruncEmulation dateTruncEmulation; + + public OracleTruncFunction(TypeConfiguration typeConfiguration) { + super( + "trunc(?1)", + "trunc(?1,?2)", + DatetimeTrunc.TRUNC, + null, + typeConfiguration + ); + this.dateTruncEmulation = new DateTruncEmulation( "to_date", typeConfiguration ); + } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine, + TypeConfiguration typeConfiguration) { + final NodeBuilder nodeBuilder = queryEngine.getCriteriaBuilder(); + final List> args = new ArrayList<>( arguments ); + final FunctionRenderingSupport renderingSupport; + final ArgumentsValidator argumentsValidator; + if ( arguments.size() == 2 && arguments.get( 1 ) instanceof SqmExtractUnit ) { + // datetime truncation + renderingSupport = datetimeRenderingSupport; + argumentsValidator = TruncArgumentsValidator.DATETIME_VALIDATOR; + // the trunc() function requires translating the temporal_unit to a format string + final TemporalUnit temporalUnit = ( (SqmExtractUnit) arguments.get( 1 ) ).getUnit(); + final String pattern; + switch ( temporalUnit ) { + case YEAR: + pattern = "YYYY"; + break; + case MONTH: + pattern = "MM"; + break; + case WEEK: + pattern = "IW"; + break; + case DAY: + pattern = "DD"; + break; + case HOUR: + pattern = "HH"; + break; + case MINUTE: + pattern = "MI"; + break; + case SECOND: + // Oracle does not support truncating to seconds with the native function, use emulation + return dateTruncEmulation.generateSqmFunctionExpression( + arguments, + impliedResultType, + queryEngine, + typeConfiguration + ); + default: + throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" ); + } + // replace temporal_unit parameter with translated string format literal + args.set( 1, new SqmLiteral<>( + pattern, + typeConfiguration.getBasicTypeForJavaType( String.class ), + nodeBuilder + ) ); + } + else { + // numeric truncation + renderingSupport = numericRenderingSupport; + argumentsValidator = TruncArgumentsValidator.NUMERIC_VALIDATOR; + } + + return new SelfRenderingSqmFunction<>( + this, + renderingSupport, + args, + impliedResultType, + argumentsValidator, + getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + getName() + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/PostgreSQLTruncFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/PostgreSQLTruncFunction.java new file mode 100644 index 0000000000..2fc4b1ceeb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/PostgreSQLTruncFunction.java @@ -0,0 +1,66 @@ +/* + * 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.query.ReturnableType; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; +import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Custom {@link TruncFunction} for PostgreSQL which uses the dialect-specific function for numeric truncation + * + * @author Marco Belladelli + */ +public class PostgreSQLTruncFunction extends TruncFunction { + private final PostgreSQLTruncRoundFunction postgreSQLTruncRoundFunction; + + public PostgreSQLTruncFunction(boolean supportsTwoArguments, TypeConfiguration typeConfiguration) { + super( + "trunc(?1)", + null, + DatetimeTrunc.DATE_TRUNC, + null, + typeConfiguration + ); + this.postgreSQLTruncRoundFunction = new PostgreSQLTruncRoundFunction( "trunc", supportsTwoArguments ); + } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine, + TypeConfiguration typeConfiguration) { + final List> args = new ArrayList<>( arguments ); + if ( arguments.size() != 2 || !( arguments.get( 1 ) instanceof SqmExtractUnit ) ) { + // numeric truncation + return postgreSQLTruncRoundFunction.generateSqmFunctionExpression( + arguments, + impliedResultType, + queryEngine, + typeConfiguration + ); + } + // datetime truncation + return new SelfRenderingSqmFunction<>( + this, + datetimeRenderingSupport, + args, + impliedResultType, + TruncArgumentsValidator.DATETIME_VALIDATOR, + getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + getName() + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/PostgreSQLTruncRoundFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/PostgreSQLTruncRoundFunction.java index 4dafca218f..2eac7323e8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/PostgreSQLTruncRoundFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/PostgreSQLTruncRoundFunction.java @@ -8,16 +8,23 @@ package org.hibernate.dialect.function; import java.util.List; +import org.hibernate.query.ReturnableType; +import org.hibernate.query.spi.QueryEngine; +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.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.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.Expression; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.spi.TypeConfiguration; import static org.hibernate.query.sqm.produce.function.FunctionParameterType.INTEGER; import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC; @@ -41,7 +48,7 @@ import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUM * @author Marco Belladelli * @see PostgreSQL documentation */ -public class PostgreSQLTruncRoundFunction extends AbstractSqmSelfRenderingFunctionDescriptor { +public class PostgreSQLTruncRoundFunction extends AbstractSqmFunctionDescriptor implements FunctionRenderingSupport { private final boolean supportsTwoArguments; public PostgreSQLTruncRoundFunction(String name, boolean supportsTwoArguments) { @@ -96,4 +103,22 @@ public class PostgreSQLTruncRoundFunction extends AbstractSqmSelfRenderingFuncti public String getArgumentListSignature() { return "(NUMERIC number[, INTEGER places])"; } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine, + TypeConfiguration typeConfiguration) { + return new SelfRenderingSqmFunction<>( + this, + this, + arguments, + impliedResultType, + getArgumentsValidator(), + getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + getName() + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlServerConvertTruncFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlServerConvertTruncFunction.java new file mode 100644 index 0000000000..fd89483c0d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/SqlServerConvertTruncFunction.java @@ -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.dialect.function; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.query.ReturnableType; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; +import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * Custom {@link TruncFunction} for SQL Server versions < 16 which uses the custom {@link DateTruncConvertEmulation} + * + * @author Marco Belladelli + */ +public class SqlServerConvertTruncFunction extends TruncFunction { + private final DateTruncConvertEmulation dateTruncEmulation; + + public SqlServerConvertTruncFunction(TypeConfiguration typeConfiguration) { + super( + "round(?1,0,1)", + "round(?1,?2,1)", + null, + null, + typeConfiguration + ); + this.dateTruncEmulation = new DateTruncConvertEmulation( typeConfiguration ); + } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine, + TypeConfiguration typeConfiguration) { + final List> args = new ArrayList<>( arguments ); + if ( arguments.size() == 2 && arguments.get( 1 ) instanceof SqmExtractUnit ) { + // datetime truncation + return dateTruncEmulation.generateSqmExpression( + arguments, + impliedResultType, + queryEngine, + typeConfiguration + ); + } + // numeric truncation + return new SelfRenderingSqmFunction<>( + this, + numericRenderingSupport, + args, + impliedResultType, + TruncArgumentsValidator.NUMERIC_VALIDATOR, + getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + getName() + ); + } + + /** + * Custom {@link DateTruncEmulation} that handles rendering when using the convert function to parse datetime strings + * + * @author Marco Belladelli + */ + private static class DateTruncConvertEmulation extends DateTruncEmulation { + public DateTruncConvertEmulation(TypeConfiguration typeConfiguration) { + super( "convert", typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + sqlAppender.appendSql( toDateFunction ); + sqlAppender.append( '(' ); + sqlAppender.append( "datetime," ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( ')' ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/SybaseTruncFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/SybaseTruncFunction.java new file mode 100644 index 0000000000..9ad6b3ea7a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/SybaseTruncFunction.java @@ -0,0 +1,154 @@ +/* + * 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.query.ReturnableType; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.TemporalUnit; +import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; +import org.hibernate.query.sqm.tree.SqmTypedNode; +import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; +import org.hibernate.query.sqm.tree.expression.SqmLiteral; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.spi.TypeConfiguration; + +import static java.util.Arrays.asList; + +/** + * Custom {@link TruncFunction} for Sybase which uses a dialect-specific emulation function for datetimes + * + * @author Marco Belladelli + */ +public class SybaseTruncFunction extends TruncFunction { + private final SybaseDateTruncEmulation dateTruncEmulation; + + public SybaseTruncFunction(TypeConfiguration typeConfiguration) { + super( + "sign(?1)*floor(abs(?1))", + "sign(?1)*floor(abs(?1)*power(10,?2))/power(10,?2)", + null, + null, + typeConfiguration + ); + this.dateTruncEmulation = new SybaseDateTruncEmulation( "convert", typeConfiguration ); + } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine, + TypeConfiguration typeConfiguration) { + final List> args = new ArrayList<>( arguments ); + if ( arguments.size() == 2 && arguments.get( 1 ) instanceof SqmExtractUnit ) { + // datetime truncation + return dateTruncEmulation.generateSqmFunctionExpression( + arguments, + impliedResultType, + queryEngine, + typeConfiguration + ); + } + // numeric truncation + return new SelfRenderingSqmFunction<>( + this, + numericRenderingSupport, + args, + impliedResultType, + TruncArgumentsValidator.NUMERIC_VALIDATOR, + getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + getName() + ); + } + + /** + * Custom {@link DateTruncEmulation} that uses convert instead of format for Sybase + * + * @author Marco Belladelli + * @see Sybase Documentation + */ + private static class SybaseDateTruncEmulation extends DateTruncEmulation { + public SybaseDateTruncEmulation( + String toDateFunction, + TypeConfiguration typeConfiguration) { + super( toDateFunction, typeConfiguration ); + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + sqlAppender.appendSql( toDateFunction ); + sqlAppender.append( '(' ); + sqlAppender.append( "datetime,substring(convert(varchar," ); + sqlAstArguments.get( 0 ).accept( walker ); + sqlAppender.append( ",21),1,17-len(" ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( "))+" ); + sqlAstArguments.get( 1 ).accept( walker ); + sqlAppender.append( ",21)" ); + } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine, + TypeConfiguration typeConfiguration) { + final NodeBuilder nodeBuilder = queryEngine.getCriteriaBuilder(); + final TemporalUnit temporalUnit = ( (SqmExtractUnit) arguments.get( 1 ) ).getUnit(); + final String literal; + switch ( temporalUnit ) { + case YEAR: + literal = "/01/01 00:00:00"; + break; + case MONTH: + literal = "/01 00:00:00"; + break; + case DAY: + literal = " 00:00:00"; + break; + case HOUR: + literal = ":00:00"; + break; + case MINUTE: + literal = ":00"; + break; + case SECOND: + literal = ""; + break; + default: + throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" ); + } + final SqmTypedNode datetime = arguments.get( 0 ); + final SqmLiteral sqmLiteral = new SqmLiteral<>( + literal, + typeConfiguration.getBasicTypeForJavaType( String.class ), + nodeBuilder + ); + + return new SelfRenderingSqmFunction<>( + this, + this, + asList( datetime, sqmLiteral ), + impliedResultType, + null, + getReturnTypeResolver(), + nodeBuilder, + getName() + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/TruncFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/TruncFunction.java new file mode 100644 index 0000000000..8cf32ca21e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/TruncFunction.java @@ -0,0 +1,227 @@ +/* + * 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.query.ReturnableType; +import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.TemporalUnit; +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.ArgumentsValidator; +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.expression.SqmExtractUnit; +import org.hibernate.query.sqm.tree.expression.SqmLiteral; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.type.spi.TypeConfiguration; + +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.NUMERIC; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL; +import static org.hibernate.query.sqm.produce.function.FunctionParameterType.TEMPORAL_UNIT; + +/** + * Custom function that manages both numeric and datetime truncation + * + * @author Marco Belladelli + */ +public class TruncFunction extends AbstractSqmFunctionDescriptor { + protected final TruncRenderingSupport numericRenderingSupport; + protected final TruncRenderingSupport datetimeRenderingSupport; + private final DatetimeTrunc datetimeTrunc; + private final DateTruncEmulation dateTruncEmulation; + + public enum DatetimeTrunc { + DATE_TRUNC( "date_trunc('?2',?1)" ), + DATETRUNC( "datetrunc(?2,?1)" ), + TRUNC( "trunc(?1,?2)" ), + FORMAT( null ); + + private final String pattern; + + DatetimeTrunc(String pattern) { + this.pattern = pattern; + } + + public String getPattern() { + return pattern; + } + } + + public TruncFunction( + String truncPattern, + String twoArgTruncPattern, + DatetimeTrunc datetimeTrunc, + String toDateFunction, + TypeConfiguration typeConfiguration) { + super( + "trunc", + new TruncArgumentsValidator(), + StandardFunctionReturnTypeResolvers.useArgType( 1 ), + StandardFunctionArgumentTypeResolvers.ARGUMENT_OR_IMPLIED_RESULT_TYPE + ); + this.numericRenderingSupport = new TruncRenderingSupport( + new PatternRenderer( truncPattern ), + twoArgTruncPattern != null ? new PatternRenderer( twoArgTruncPattern ) : null + ); + this.datetimeTrunc = datetimeTrunc; + TruncRenderingSupport renderingSupport = null; + DateTruncEmulation emulation = null; + if ( datetimeTrunc != null ) { + if ( datetimeTrunc.getPattern() != null ) { + renderingSupport = new TruncRenderingSupport( new PatternRenderer( datetimeTrunc.getPattern() ), null ); + } + else { + emulation = new DateTruncEmulation( toDateFunction, typeConfiguration ); + } + } + this.datetimeRenderingSupport = renderingSupport; + this.dateTruncEmulation = emulation; + } + + @Override + protected SelfRenderingSqmFunction generateSqmFunctionExpression( + List> arguments, + ReturnableType impliedResultType, + QueryEngine queryEngine, + TypeConfiguration typeConfiguration) { + final NodeBuilder nodeBuilder = queryEngine.getCriteriaBuilder(); + final List> args = new ArrayList<>( arguments ); + final FunctionRenderingSupport renderingSupport; + final ArgumentsValidator argumentsValidator; + if ( arguments.size() == 2 && arguments.get( 1 ) instanceof SqmExtractUnit ) { + // datetime truncation + renderingSupport = datetimeRenderingSupport; + argumentsValidator = TruncArgumentsValidator.DATETIME_VALIDATOR; + if ( datetimeTrunc == null ) { + throw new UnsupportedOperationException( "Datetime truncation is not supported for this database" ); + } + if ( datetimeTrunc.getPattern() == null ) { + return dateTruncEmulation.generateSqmFunctionExpression( + arguments, + impliedResultType, + queryEngine, + typeConfiguration + ); + } + else if ( datetimeTrunc == DatetimeTrunc.TRUNC ) { + // the trunc() function requires translating the temporal_unit to a format string + final TemporalUnit temporalUnit = ( (SqmExtractUnit) arguments.get( 1 ) ).getUnit(); + final String pattern; + switch ( temporalUnit ) { + case YEAR: + pattern = "YYYY"; + break; + case MONTH: + pattern = "MM"; + break; + case WEEK: + pattern = "IW"; + break; + case DAY: + pattern = "DD"; + break; + case HOUR: + pattern = "HH"; + break; + case MINUTE: + pattern = "MI"; + break; + case SECOND: + pattern = "SS"; + break; + default: + throw new UnsupportedOperationException( "Temporal unit not supported [" + temporalUnit + "]" ); + } + // replace temporal_unit parameter with translated string format literal + args.set( 1, new SqmLiteral<>( + pattern, + typeConfiguration.getBasicTypeForJavaType( String.class ), + nodeBuilder + ) ); + } + } + else { + // numeric truncation + renderingSupport = numericRenderingSupport; + argumentsValidator = TruncArgumentsValidator.NUMERIC_VALIDATOR; + } + + return new SelfRenderingSqmFunction<>( + this, + renderingSupport, + args, + impliedResultType, + argumentsValidator, + getReturnTypeResolver(), + queryEngine.getCriteriaBuilder(), + getName() + ); + } + + private static class TruncRenderingSupport implements FunctionRenderingSupport { + private final PatternRenderer truncPattern; + private final PatternRenderer twoArgTruncPattern; + + public TruncRenderingSupport(PatternRenderer truncPattern, PatternRenderer twoArgTruncPattern) { + this.truncPattern = truncPattern; + this.twoArgTruncPattern = twoArgTruncPattern; + } + + @Override + public void render( + SqlAppender sqlAppender, + List sqlAstArguments, + SqlAstTranslator walker) { + final PatternRenderer pattern; + if ( twoArgTruncPattern != null && sqlAstArguments.size() == 2 ) { + pattern = twoArgTruncPattern; + } + else { + pattern = truncPattern; + } + pattern.render( sqlAppender, sqlAstArguments, walker ); + } + } + + protected static class TruncArgumentsValidator implements ArgumentsValidator { + protected static final ArgumentTypesValidator DATETIME_VALIDATOR = new ArgumentTypesValidator( + StandardArgumentsValidators.exactly( 2 ), + TEMPORAL, + TEMPORAL_UNIT + ); + + protected static final ArgumentTypesValidator NUMERIC_VALIDATOR = new ArgumentTypesValidator( + StandardArgumentsValidators.between( 1, 2 ), + NUMERIC, + NUMERIC + ); + + @Override + public void validate( + List> arguments, + String functionName, + TypeConfiguration typeConfiguration) { + if ( arguments.size() == 2 && arguments.get( 1 ) instanceof SqmExtractUnit ) { + DATETIME_VALIDATOR.validate( arguments, functionName, typeConfiguration ); + } + else { + NUMERIC_VALIDATOR.validate( arguments, functionName, typeConfiguration ); + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java index 6617bfc858..f267a4acdf 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/SemanticQueryBuilder.java @@ -4327,6 +4327,25 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implem ); } + @Override + public Object visitTruncFunction(HqlParser.TruncFunctionContext ctx) { + final SqmExpression expression = (SqmExpression) ctx.getChild( 2 ).accept( this ); + final SqmTypedNode secondArg; + if ( ctx.getChildCount() == 6 ) { + secondArg = (SqmTypedNode) ctx.getChild( 4 ).accept( this ); + } + else { + secondArg = null; + } + + return getFunctionDescriptor( "trunc" ).generateSqmExpression( + secondArg == null ? singletonList( expression ) : asList( expression, secondArg ), + null, + creationContext.getQueryEngine(), + creationContext.getJpaMetamodel().getTypeConfiguration() + ); + } + @Override public Object visitFormat(HqlParser.FormatContext ctx) { final String format = QuotingHelper.unquoteStringLiteral( ctx.getChild( 0 ).getText() ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaBuilderNonStandardFunctionsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaBuilderNonStandardFunctionsTest.java index 66d2c49f48..16f612916e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaBuilderNonStandardFunctionsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaBuilderNonStandardFunctionsTest.java @@ -16,7 +16,6 @@ import java.util.Date; import java.util.List; import org.hibernate.dialect.CockroachDialect; -import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.query.criteria.HibernateCriteriaBuilder; @@ -24,6 +23,7 @@ import org.hibernate.testing.orm.domain.StandardDomainModel; import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.SessionFactory; @@ -435,4 +435,32 @@ public class CriteriaBuilderNonStandardFunctionsTest { assertEquals( Math.PI, result.get( 1, Double.class ), 1e-9 ); } ); } + + @Test + @JiraKey("HHH-16185") + public void testCustomFunctionTrunc(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createTupleQuery(); + query.multiselect( + cb.function( "trunc", Float.class, cb.literal( 32.92345f ) ), + cb.function( "truncate", Float.class, cb.literal( 32.92345f ) ), + cb.function( "trunc", Float.class, cb.literal( 32.92345f ), cb.literal( 3 ) ), + cb.function( "truncate", Float.class, cb.literal( 32.92345f ), cb.literal( 3 ) ), + cb.function( "trunc", Double.class, cb.literal( 32.92345d ) ), + cb.function( "truncate", Double.class, cb.literal( 32.92345d ) ), + cb.function( "trunc", Double.class, cb.literal( 32.92345d ), cb.literal( 3 ) ), + cb.function( "truncate", Double.class, cb.literal( 32.92345d ), cb.literal( 3 ) ) + ); + final Tuple result = session.createQuery( query ).getSingleResult(); + assertEquals( 32f, result.get( 0 ) ); + assertEquals( 32f, result.get( 1 ) ); + assertEquals( 32.923f, result.get( 2 ) ); + assertEquals( 32.923f, result.get( 3 ) ); + assertEquals( 32d, result.get( 4 ) ); + assertEquals( 32d, result.get( 5 ) ); + assertEquals( 32.923d, result.get( 6 ) ); + assertEquals( 32.923d, result.get( 7 ) ); + } ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java index 392c8eccf8..29fba74b61 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java @@ -517,12 +517,12 @@ public class FunctionTests { public void testDateTruncFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery( "select date_trunc(year,current_timestamp)", Timestamp.class ).getSingleResult(); - session.createQuery( "select date_trunc(month,current_timestamp)", Timestamp.class ).getSingleResult(); - session.createQuery( "select date_trunc(day,current_timestamp)", Timestamp.class ).getSingleResult(); - session.createQuery( "select date_trunc(hour,current_timestamp)", Timestamp.class ).getSingleResult(); - session.createQuery( "select date_trunc(minute,current_timestamp)", Timestamp.class ).getSingleResult(); - session.createQuery( "select date_trunc(second,current_timestamp)", Timestamp.class ).getSingleResult(); + session.createQuery( "select trunc(current_timestamp,year)", Timestamp.class ).getSingleResult(); + session.createQuery( "select trunc(current_timestamp,month)", Timestamp.class ).getSingleResult(); + session.createQuery( "select trunc(current_timestamp,day)", Timestamp.class ).getSingleResult(); + session.createQuery( "select truncate(current_timestamp,hour)", Timestamp.class ).getSingleResult(); + session.createQuery( "select truncate(current_timestamp,minute)", Timestamp.class ).getSingleResult(); + session.createQuery( "select truncate(current_timestamp,second)", Timestamp.class ).getSingleResult(); } ); }