From eab6107ec2e7b3a0c06146a9ff51b9964f4b3169 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Mon, 6 Jan 2020 08:38:08 -0600 Subject: [PATCH] HHH-13785 : HQL/Criteria function support - temporal literals - generalized literals - literal formatters (SQL string generation) - FORMAT function --- design/doc-query-expressions.adoc | 33 + design/doc-temporal.adoc | 10 + gradle/libraries.gradle | 2 +- .../org/hibernate/grammars/hql/HqlLexer.g4 | 103 ++- .../org/hibernate/grammars/hql/HqlParser.g4 | 85 ++- .../java/org/hibernate/dialect/Dialect.java | 1 - .../java/org/hibernate/dialect/H2Dialect.java | 14 + .../hibernate/dialect/InformixDialect.java | 60 ++ .../org/hibernate/dialect/IngresDialect.java | 16 + .../org/hibernate/dialect/MySQLDialect.java | 75 ++ .../hibernate/dialect/Oracle8iDialect.java | 1 + .../dialect/PostgreSQL81Dialect.java | 1 + .../hibernate/dialect/SQLServerDialect.java | 54 ++ .../function/CommonFunctionFactory.java | 1 - .../dialect/{ => function}/Replacer.java | 9 +- .../entity/JoinedSubclassEntityPersister.java | 30 +- .../entity/SingleTableEntityPersister.java | 2 +- .../query/QueryLiteralRendering.java | 3 + .../hql/internal/SemanticQueryBuilder.java | 714 +++++++++++++++--- .../sqm/sql/BaseSqmToSqlAstConverter.java | 23 +- .../BasicValuedPathInterpretation.java | 14 +- .../EmbeddableValuedPathInterpretation.java | 5 +- .../EntityValuedPathInterpretation.java | 7 +- .../sql/internal/SqmPathInterpretation.java | 7 +- .../sqm/tree/expression/SqmFunction.java | 4 + .../SqmSelfRenderingExpression.java | 35 + .../query/sqm/tree/select/SqmSelection.java | 4 + .../sql/ast/spi/AbstractSqlAstWalker.java | 3 + .../sql/ast/tree/cte/CteTableGroup.java | 6 + .../BinaryArithmeticExpression.java | 62 +- .../sql/ast/tree/from/AbstractTableGroup.java | 8 +- .../ast/tree/from/CompositeTableGroup.java | 8 +- .../MutatingTableReferenceGroupWrapper.java | 6 + .../sql/ast/tree/from/TableGroup.java | 3 +- .../sql/ast/tree/from/UnionTableGroup.java | 6 + .../EntityCollectionPartTableGroup.java | 6 + .../type/descriptor/DateTimeUtils.java | 91 +++ .../descriptor/sql/BigIntTypeDescriptor.java | 14 + .../descriptor/sql/BitTypeDescriptor.java | 7 + .../descriptor/sql/BlobTypeDescriptor.java | 7 + .../descriptor/sql/BooleanTypeDescriptor.java | 14 + .../descriptor/sql/DateTypeDescriptor.java | 16 + .../descriptor/sql/DecimalTypeDescriptor.java | 14 + .../descriptor/sql/DoubleTypeDescriptor.java | 14 + .../descriptor/sql/FloatTypeDescriptor.java | 8 + .../descriptor/sql/IntegerTypeDescriptor.java | 14 + .../descriptor/sql}/JdbcLiteralFormatter.java | 2 +- .../sql/NVarcharTypeDescriptor.java | 14 +- .../descriptor/sql/RealTypeDescriptor.java | 14 + .../sql/SmallIntTypeDescriptor.java | 14 + .../descriptor/sql/SqlTypeDescriptor.java | 1 - .../descriptor/sql/TimeTypeDescriptor.java | 16 + .../sql/TimestampTypeDescriptor.java | 16 + .../descriptor/sql/TinyIntTypeDescriptor.java | 14 + .../sql/VarbinaryTypeDescriptor.java | 7 + .../descriptor/sql/VarcharTypeDescriptor.java | 14 +- .../internal/JdbcLiteralFormatterBoolean.java | 28 + .../JdbcLiteralFormatterCharacterData.java | 45 ++ .../JdbcLiteralFormatterNumericData.java | 31 + .../JdbcLiteralFormatterTemporal.java | 71 ++ .../sql/spi/AbstractJdbcLiteralFormatter.java | 27 + .../sql/spi/BasicJdbcLiteralFormatter.java | 34 + .../orm/test/query/hql/LiteralTests.java | 85 ++- .../test/query/hql/StandardFunctionTests.java | 324 ++++---- .../orm/domain/gambit/EntityOfBasics.java | 50 ++ 65 files changed, 2068 insertions(+), 359 deletions(-) create mode 100644 design/doc-query-expressions.adoc create mode 100644 design/doc-temporal.adoc rename hibernate-core/src/main/java/org/hibernate/dialect/{ => function}/Replacer.java (93%) create mode 100644 hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java rename hibernate-core/src/main/java/org/hibernate/{sql/ast/spi => type/descriptor/sql}/JdbcLiteralFormatter.java (95%) create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterBoolean.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterCharacterData.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterNumericData.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterTemporal.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/AbstractJdbcLiteralFormatter.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/BasicJdbcLiteralFormatter.java diff --git a/design/doc-query-expressions.adoc b/design/doc-query-expressions.adoc new file mode 100644 index 0000000000..7e79333c9b --- /dev/null +++ b/design/doc-query-expressions.adoc @@ -0,0 +1,33 @@ += Query + +== Expressions + +=== Paths + +=== Literals + +=== Parameters + +=== Unary expressions + +=== Arithmetic operations + +Numeric v. temporal + +=== Functions + +- Standard functions +- SqmFunctionRegistry +- Role of Dialect + +=== Concatenation operations + +=== Entity-type references + +=== CASE statements + +=== COALESCE statements + +=== NULLIF statements + +=== Sub-queries \ No newline at end of file diff --git a/design/doc-temporal.adoc b/design/doc-temporal.adoc new file mode 100644 index 0000000000..ff21ddba98 --- /dev/null +++ b/design/doc-temporal.adoc @@ -0,0 +1,10 @@ +`OffsetDateTime` is not safe to store in database. This form does not understand "zone rules" relating to things +such as DST. An offset of +5, e.g., does not change when DST starts/ends - its just +5. + +A `ZonedDateTime` on the other hand knows the actual timezone as well as the offset for the LocalDateTime portion in +that timezone. It is much more complete picture of the actual Instant. + +The proper solution for storing "with tz" would be to always use a `ZonedDateTime`, converted from `OffsetDateTime` +if needed. In this case, I assume we need to transform a `LocalDateTime` to `ZonedDateTime`? + +^^ what about Dialects that do not support "with tz" datatype variants? Are there any anymore? diff --git a/gradle/libraries.gradle b/gradle/libraries.gradle index 87c6abc0b2..5e60a40d6a 100644 --- a/gradle/libraries.gradle +++ b/gradle/libraries.gradle @@ -13,7 +13,7 @@ ext { junitVintageVersion = '5.3.1' junit5Version = '5.3.1' - h2Version = '1.4.196' + h2Version = '1.4.199' bytemanVersion = '4.0.8' //Compatible with JDK14 jnpVersion = '5.0.6.CR1' 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 84de537e20..df6a330ad3 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 @@ -11,60 +11,91 @@ lexer grammar HqlLexer; package org.hibernate.grammars.hql; } -WS : ( ' ' | '\t' | '\f' | EOL ) -> skip; +WS : WS_CHAR+ -> skip; fragment -EOL : [\r\n]+; - -INTEGER_LITERAL : INTEGER_NUMBER ; +WS_CHAR : [ \f\t\r\n]; fragment -INTEGER_NUMBER : ('0' | '1'..'9' '0'..'9'*) ; - -LONG_LITERAL : INTEGER_NUMBER ('l'|'L'); - -BIG_INTEGER_LITERAL : INTEGER_NUMBER ('bi'|'BI') ; - -HEX_LITERAL : '0' ('x'|'X') HEX_DIGIT+ ('l'|'L')? ; +DIGIT : [0-9]; fragment -HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ; +HEX_DIGIT : [0-9a-fA-F]; -OCTAL_LITERAL : '0' ('0'..'7')+ ('l'|'L')? ; +fragment +EXPONENT : [eE] [+-]? DIGIT+; -FLOAT_LITERAL : FLOATING_POINT_NUMBER ('f'|'F')? ; +fragment +LONG_SUFFIX : [lL]; + +fragment +FLOAT_SUFFIX : [fF]; + +fragment +DOUBLE_SUFFIX : [dD]; + +fragment +BIG_DECIMAL_SUFFIX : [bB] [dD]; + +fragment +BIG_INTEGER_SUFFIX : [bB] [iI]; + +fragment +INTEGER_NUMBER + : DIGIT+ + ; fragment FLOATING_POINT_NUMBER - : ('0'..'9')+ '.' ('0'..'9')* EXPONENT? - | '.' ('0'..'9')+ EXPONENT? - | ('0'..'9')+ EXPONENT - | ('0'..'9')+ + : DIGIT+ '.' DIGIT* EXPONENT? + | '.' DIGIT+ EXPONENT? + | DIGIT+ EXPONENT + | DIGIT+ ; -DOUBLE_LITERAL : FLOATING_POINT_NUMBER ('d'|'D') ; -BIG_DECIMAL_LITERAL : FLOATING_POINT_NUMBER ('bd'|'BD') ; -fragment -EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ; +fragment SINGLE_QUOTE : '\''; +fragment DOUBLE_QUOTE : '"'; -CHARACTER_LITERAL - : '\'' ( ESCAPE_SEQUENCE | ~('\''|'\\') ) '\'' {setText(getText().substring(1, getText().length()-1));} - ; - -STRING_LITERAL - : '"' ( ESCAPE_SEQUENCE | ~('\\'|'"') )* '"' {setText(getText().substring(1, getText().length()-1));} - | ('\'' ( ESCAPE_SEQUENCE | ~('\\'|'\'') )* '\'')+ {setText(getText().substring(1, getText().length()-1).replace("''", "'"));} - ; +fragment BACKSLASH : '\\'; fragment ESCAPE_SEQUENCE - : '\\' ('b'|'t'|'n'|'f'|'r'|'\\"'|'\''|'\\') - | UNICODE_ESCAPE - | OCTAL_ESCAPE + : BACKSLASH [btnfr"'] + | BACKSLASH UNICODE_ESCAPE + | BACKSLASH BACKSLASH ; +fragment +UNICODE_ESCAPE + : 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT + ; + +INTEGER_LITERAL : INTEGER_NUMBER ; + +LONG_LITERAL : INTEGER_NUMBER LONG_SUFFIX; + +FLOAT_LITERAL : FLOATING_POINT_NUMBER FLOAT_SUFFIX?; + +DOUBLE_LITERAL : FLOATING_POINT_NUMBER DOUBLE_SUFFIX; + +BIG_INTEGER_LITERAL : INTEGER_NUMBER BIG_INTEGER_SUFFIX; + +BIG_DECIMAL_LITERAL : FLOATING_POINT_NUMBER BIG_DECIMAL_SUFFIX; + +HEX_LITERAL : '0' [xX] HEX_DIGIT+ LONG_SUFFIX?; + +OCTAL_LITERAL : '0' ('0'..'7')+ ('l'|'L')? ; + +STRING_LITERAL + : DOUBLE_QUOTE ( ~[\\"] | ESCAPE_SEQUENCE | DOUBLE_QUOTE DOUBLE_QUOTE )* DOUBLE_QUOTE + { setText(getText().substring(1, getText().length()-1).replace("\"\"", "\"")); } + | SINGLE_QUOTE ( ~[\\'] | ESCAPE_SEQUENCE | SINGLE_QUOTE SINGLE_QUOTE )* SINGLE_QUOTE + { setText(getText().substring(1, getText().length()-1).replace("''", "'")); } + ; + + fragment OCTAL_ESCAPE : '\\' ('0'..'3') ('0'..'7') ('0'..'7') @@ -72,10 +103,6 @@ OCTAL_ESCAPE | '\\' ('0'..'7') ; -fragment -UNICODE_ESCAPE - : '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT - ; // ESCAPE start tokens TIMESTAMP_ESCAPE_START : '{ts'; @@ -134,6 +161,7 @@ CONCAT : [cC] [oO] [nN] [cC] [aA] [tT]; COUNT : [cC] [oO] [uU] [nN] [tT]; CROSS : [cC] [rR] [oO] [sS] [sS]; CURRENT_DATE : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [dD] [aA] [tT] [eE]; +CURRENT_DATETIME : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [dD] [aA] [tT] [eE] [tT] [iI] [mM] [eE]; CURRENT_INSTANT : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [iI] [nN] [sS] [tT] [aA] [nN] [tT]; CURRENT_TIME : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [tT] [iI] [mM] [eE]; CURRENT_TIMESTAMP : [cC] [uU] [rR] [rR] [eE] [nN] [tT] '_' [tT] [iI] [mM] [eE] [sS] [tT] [aA] [mM] [pP]; @@ -155,6 +183,7 @@ FETCH : [fF] [eE] [tT] [cC] [hH]; FLOOR : [fF] [lL] [oO] [oO] [rR]; FROM : [fF] [rR] [oO] [mM]; FOR : [fF] [oO] [rR]; +FORMAT : [fF] [oO] [rR] [mM] [aA] [tT]; FULL : [fF] [uU] [lL] [lL]; FUNCTION : [fF] [uU] [nN] [cC] [tT] [iI] [oO] [nN]; GREATEST : [gG] [rR] [eE] [aA] [tT] [eE] [sS] [tT]; 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 dd02cee776..a8ed67777a 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 @@ -466,7 +466,6 @@ nullIf literal : STRING_LITERAL - | CHARACTER_LITERAL | INTEGER_LITERAL | LONG_LITERAL | BIG_INTEGER_LITERAL @@ -478,40 +477,79 @@ literal | NULL | TRUE | FALSE - | timestampLiteral - | dateLiteral - | timeLiteral + | temporalLiteral + | generalizedLiteral ; -// todo (6.0) : expand temporal literal support to Java 8 temporal types -// * Instant -> {instant '...'} -// * LocalDate -> {localDate '...'} -// * LocalDateTime -> {localDateTime '...'} -// * OffsetDateTime -> {offsetDateTime '...'} -// * OffsetTime -> {offsetTime '...'} -// * ZonedDateTime -> {localDate '...'} -// * ... -// -// Few things: -// 1) the markers above are just initial thoughts. They are obviously verbose. Maybe acronyms or shortened forms would be better -// 2) we may want to stay away from all of the timezone headaches by not supporting local, zoned and offset forms +temporalLiteral + : dateTimeLiteral + | dateLiteral + | timeLiteral + | jdbcTimestampLiteral + | jdbcDateLiteral + | jdbcTimeLiteral + ; -timestampLiteral - : TIMESTAMP_ESCAPE_START dateTimeLiteralText RIGHT_BRACE +dateTimeLiteral + : LEFT_BRACE dateTime RIGHT_BRACE ; dateLiteral - : DATE_ESCAPE_START dateTimeLiteralText RIGHT_BRACE + : LEFT_BRACE date RIGHT_BRACE ; timeLiteral - : TIME_ESCAPE_START dateTimeLiteralText RIGHT_BRACE + : LEFT_BRACE time RIGHT_BRACE ; -dateTimeLiteralText - : STRING_LITERAL | CHARACTER_LITERAL +dateTime + : date time (zoneId | offset)? ; +date + : year MINUS month MINUS day + ; + +time + : hour COLON minute (COLON second)? + ; + +offset + : (PLUS | MINUS) hour (COLON minute)? + ; + +year: INTEGER_LITERAL; +month: INTEGER_LITERAL; +day: INTEGER_LITERAL; +hour: INTEGER_LITERAL; +minute: INTEGER_LITERAL; +second: INTEGER_LITERAL | FLOAT_LITERAL; +zoneId: STRING_LITERAL; + +jdbcTimestampLiteral + : TIMESTAMP_ESCAPE_START (dateTime | genericTemporalLiteralText) RIGHT_BRACE + ; + +jdbcDateLiteral + : DATE_ESCAPE_START (date | genericTemporalLiteralText) RIGHT_BRACE + ; + +jdbcTimeLiteral + : TIME_ESCAPE_START (time | genericTemporalLiteralText) RIGHT_BRACE + ; + +genericTemporalLiteralText + : STRING_LITERAL + ; + +generalizedLiteral + : LEFT_BRACE generalizedLiteralType COLON generalizedLiteralText RIGHT_BRACE + ; + +generalizedLiteralType : STRING_LITERAL; +generalizedLiteralText : STRING_LITERAL; + + parameter : COLON identifier # NamedParameter | QUESTION_MARK INTEGER_LITERAL? # PositionalParameter @@ -589,6 +627,7 @@ countFunction standardFunction : castFunction | extractFunction + | formatFunction | concatFunction | substringFunction | replaceFunction @@ -666,7 +705,7 @@ trimSpecification ; trimCharacter - : CHARACTER_LITERAL | STRING_LITERAL + : STRING_LITERAL ; upperFunction diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index 5407ea9ca0..11ee89c18c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -19,7 +19,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.time.temporal.TemporalAccessor; - import java.util.Calendar; import java.util.Date; import java.util.HashSet; 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 828051cbcf..08d51070d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -15,6 +15,7 @@ import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.H2ExtractEmulation; +import org.hibernate.dialect.function.Replacer; import org.hibernate.dialect.hint.IndexQueryHintHandler; import org.hibernate.dialect.identity.H2IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; @@ -205,6 +206,19 @@ public class H2Dialect extends Dialect { sqlAppender.append(")"); } + public String translateExtractField(TemporalUnit unit) { + switch ( unit ) { + case DAY_OF_MONTH: return "day"; + case WEEK: return "iso_week"; + default: return unit.toString(); + } + } + + @Override + public String translateDatetimeFormat(String format) { + return new Replacer( format, "'", "''" ).replace( "e", "u").result(); //NICE!! + } + @Override public String getAddColumnString() { return "add column"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java index 23eb266a73..8825713b8a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java @@ -13,6 +13,7 @@ import java.util.Locale; import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.InformixExtractEmulation; +import org.hibernate.dialect.function.Replacer; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.InformixIdentityColumnSupport; import org.hibernate.dialect.pagination.FirstLimitHandler; @@ -117,6 +118,65 @@ public class InformixDialect extends Dialect { } + @Override + public String translateDatetimeFormat(String format) { + //Informix' own variation of MySQL + return datetimeFormat( format ).result(); + } + + public static Replacer datetimeFormat(String format) { + return new Replacer( format, "'", "" ) + .replace("%", "%%") + + //year + .replace("yyyy", "%Y") + .replace("yyy", "%Y") + .replace("yy", "%y") + .replace("y", "Y") + + //month of year + .replace("MMMM", "%B") + .replace("MMM", "%b") + .replace("MM", "%m") + .replace("M", "%c") //???? + + //day of week + .replace("EEEE", "%A") + .replace("EEE", "%a") + .replace("ee", "%w") + .replace("e", "%w") + + //day of month + .replace("dd", "%d") + .replace("d", "%e") + + //am pm + .replace("aa", "%p") //????? + .replace("a", "%p") //????? + + //hour + .replace("hh", "%I") + .replace("HH", "%H") + .replace("h", "%I") + .replace("H", "%H") + + //minute + .replace("mm", "%M") + .replace("m", "%M") + + //second + .replace("ss", "%S") + .replace("s", "%S") + + //fractional seconds + .replace("SSSSSS", "%F50") //5 is the max + .replace("SSSSS", "%F5") + .replace("SSSS", "%F4") + .replace("SSS", "%F3") + .replace("SS", "%F2") + .replace("S", "%F1"); + } + @Override public String getAddColumnString() { return "add"; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/IngresDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/IngresDialect.java index 7ebf776a77..4ea23ebb2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/IngresDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/IngresDialect.java @@ -188,6 +188,22 @@ public class IngresDialect extends Dialect { sqlAppender.append(")"); } + @Override + public String translateDatetimeFormat(String format) { + return MySQLDialect.datetimeFormat( format ).result(); + } + + @Override + public String translateExtractField(TemporalUnit unit) { + switch ( unit ) { + case DAY_OF_MONTH: return "day"; + case DAY_OF_YEAR: return "doy"; + case DAY_OF_WEEK: return "dow"; + case WEEK: return "iso_week"; + default: return unit.toString(); + } + } + @Override public String getSelectGUIDString() { return "select uuid_to_char(uuid_create())"; 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 f77b0cad51..6c34dd7b2f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -20,6 +20,7 @@ import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.MySQLExtractEmulation; +import org.hibernate.dialect.function.Replacer; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.MySQLIdentityColumnSupport; import org.hibernate.dialect.pagination.AbstractLimitHandler; @@ -242,6 +243,80 @@ public class MySQLDialect extends Dialect { } } + @Override + public String translateDatetimeFormat(String format) { + return datetimeFormat( format ).result(); + } + + public static Replacer datetimeFormat(String format) { + return new Replacer( format, "'", "" ) + .replace("%", "%%") + + //year + .replace("yyyy", "%Y") + .replace("yyy", "%Y") + .replace("yy", "%y") + .replace("y", "%Y") + + //month of year + .replace("MMMM", "%M") + .replace("MMM", "%b") + .replace("MM", "%m") + .replace("M", "%c") + + //week of year + .replace("ww", "%v") + .replace("w", "%v") + //year for week + .replace("YYYY", "%x") + .replace("YYY", "%x") + .replace("Y", "%x") + + //week of month + //???? + + //day of week + .replace("EEEE", "%W") + .replace("EEE", "%a") + .replace("ee", "%w") + .replace("e", "%w") + + //day of month + .replace("dd", "%d") + .replace("d", "%e") + + //day of year + .replace("DDD", "%j") + .replace("DD", "%j") + .replace("D", "%j") + + //am pm + .replace("aa", "%p") + .replace("a", "%p") + + //hour + .replace("hh", "%h") + .replace("HH", "%H") + .replace("h", "%l") + .replace("H", "%k") + + //minute + .replace("mm", "%i") + .replace("m", "%i") + + //second + .replace("ss", "%S") + .replace("s", "%S") + + //fractional seconds + .replace("SSSSSS", "%f") + .replace("SSSSS", "%f") + .replace("SSSS", "%f") + .replace("SSS", "%f") + .replace("SS", "%f") + .replace("S", "%f"); + } + void upgradeTo57() { // For details about MySQL 5.7 support for fractional seconds diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java index 993a340537..1f765c8473 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Oracle8iDialect.java @@ -21,6 +21,7 @@ import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.NvlCoalesceEmulation; import org.hibernate.dialect.function.OracleExtractEmulation; +import org.hibernate.dialect.function.Replacer; import org.hibernate.dialect.pagination.AbstractLimitHandler; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.LimitHelper; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java index e5708c7173..cc5b254ab9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQL81Dialect.java @@ -22,6 +22,7 @@ import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.PostgresExtractEmulation; +import org.hibernate.dialect.function.Replacer; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.PostgreSQL81IdentityColumnSupport; import org.hibernate.dialect.pagination.AbstractLimitHandler; 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 2ee420f11e..b9c861db91 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -12,12 +12,14 @@ import java.util.Locale; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.function.Replacer; import org.hibernate.dialect.function.TransactSQLTrimEmulation; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.SQLServerIdentityColumnSupport; import org.hibernate.dialect.pagination.LegacyLimitHandler; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.TopLimitHandler; +import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; import org.hibernate.type.descriptor.sql.SmallIntTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; @@ -59,6 +61,58 @@ public class SQLServerDialect extends AbstractTransactSQLDialect { queryEngine.getSqmFunctionRegistry().register( "trim", new TransactSQLTrimEmulation() ); } + @Override + public String translateExtractField(TemporalUnit unit) { + switch ( unit ) { + case WEEK: return "isowk"; //the ISO week number (behavior of "week" depends on a system property) + default: return super.translateExtractField(unit); + } + } + + @Override + public String translateDatetimeFormat(String format) { + return datetimeFormat(format).result(); + } + + public static Replacer datetimeFormat(String format) { + return new Replacer( format, "'", "\"" ) + //era + .replace("G", "g") + + //y nothing to do + //M nothing to do + + //w no equivalent + //W no equivalent + //Y no equivalent + + //day of week + .replace("EEEE", "dddd") + .replace("EEE", "ddd") + //e no equivalent + + //d nothing to do + //D no equivalent + + //am pm + .replace("aa", "tt") + .replace("a", "tt") + + //h nothing to do + //H nothing to do + + //m nothing to do + //s nothing to do + + //fractional seconds + .replace("S", "F") + + //timezones + .replace("XXX", "K") //UTC represented as "Z" + .replace("xxx", "zzz") + .replace("x", "zz"); + } + @Override public String getNoColumnsInsertString() { return "default values"; 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 6176651774..2910438b99 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 @@ -7,7 +7,6 @@ package org.hibernate.dialect.function; import org.hibernate.query.spi.QueryEngine; -import org.hibernate.query.sqm.function.StandardFunctionRenderingSupport; import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.type.StandardBasicTypes; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Replacer.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/Replacer.java similarity index 93% rename from hibernate-core/src/main/java/org/hibernate/dialect/Replacer.java rename to hibernate-core/src/main/java/org/hibernate/dialect/function/Replacer.java index 232dfe9b0f..8a8ba8d448 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Replacer.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/Replacer.java @@ -4,21 +4,24 @@ * 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; +package org.hibernate.dialect.function; import java.util.ArrayList; import java.util.List; +import org.hibernate.Internal; + /** * @author Gavin King */ +@Internal public class Replacer { private String[] chunks; private String quote; private String delimiter; private List replacements = new ArrayList<>(); - class Replacement { + public static class Replacement { String placeholder; String replacement; @@ -45,7 +48,7 @@ public class Replacer { } } - Replacer(String format, String quote, String delimiter) { + public Replacer(String format, String quote, String delimiter) { this.delimiter = delimiter; this.chunks = format.split( quote ); this.quote = quote; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index 7afaf9fbea..7a634ef16a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -157,7 +157,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { private final boolean[] isNullableTable; private final boolean[] isInverseTable; - private final Map discriminatorValuesByTableName; + private final Map discriminatorValuesByTableName; private final Map subclassNameByTableName; //INITIALIZATION: @@ -532,7 +532,22 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { discriminatorValuesByTableName = new LinkedHashMap<>( subclassSpan + 1 ); subclassNameByTableName = new HashMap<>( subclassSpan + 1); - discriminatorValuesByTableName.put( persistentClass.getTable().getName(), discriminatorSQLString); + // We need to convert the `discriminatorSQLString` (which is a String read from boot-mapping) into + // the type indicated by `#discriminatorType` (String -> Integer, e.g.). + try { + final Object convertedDiscriminatorValue = discriminatorType.stringToObject( discriminatorSQLString ); + discriminatorValuesByTableName.put( persistentClass.getTable().getName(), convertedDiscriminatorValue ); + } + catch (HibernateException e) { + throw e; + } + catch (Exception e) { + throw new MappingException( + "Could not resolve specified discriminator value [" + discriminatorSQLString + + "] to discriminator type [" + discriminatorType + "]" + + ); + } discriminatorValues = new String[subclassSpan]; discriminatorValues[subclassSpan - 1] = discriminatorSQLString; notNullColumnTableNumbers = new int[subclassSpan]; @@ -590,7 +605,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { // "foo.class = Bar" works in HQL discriminatorValue = sc.getSubclassId(); } - discriminatorValuesByTableName.put( sc.getTable().getName(), discriminatorValue.toString() ); + discriminatorValuesByTableName.put( sc.getTable().getName(), discriminatorValue ); subclassesByDiscriminatorValue.put( discriminatorValue, sc.getEntityName() ); discriminatorValues[k] = discriminatorValue.toString(); int id = getTableId( @@ -1320,11 +1335,10 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { ColumnReference identifierColumnReference, BasicType resultType) { final Predicate predicate = new NullnessPredicate( identifierColumnReference, true ); - final Expression expression = - new QueryLiteral<>( - discriminatorValuesByTableName.get( table.getTableExpression() ), - resultType - ); + final Expression expression = new QueryLiteral<>( + discriminatorValuesByTableName.get( table.getTableExpression() ), + resultType + ); caseSearchedExpression.when( predicate, expression ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index 6bc2b630ea..13ea43ee31 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -103,7 +103,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { private final int[] subclassFormulaTableNumberClosure; // discriminator column - private final Map subclassesByDiscriminatorValue = new HashMap(); + private final Map subclassesByDiscriminatorValue = new HashMap<>(); private final boolean forceDiscriminator; private final String discriminatorColumnName; private final String discriminatorColumnReaders; diff --git a/hibernate-core/src/main/java/org/hibernate/query/QueryLiteralRendering.java b/hibernate-core/src/main/java/org/hibernate/query/QueryLiteralRendering.java index 6c621331fc..d4b5ee8585 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/QueryLiteralRendering.java +++ b/hibernate-core/src/main/java/org/hibernate/query/QueryLiteralRendering.java @@ -36,6 +36,9 @@ public enum QueryLiteralRendering { /** * As a parameter when the literal occurs outside the SELECT clause, * otherwise render as a SQL literal. + * + * Technically this should be called something like AS_PARAM_UNLESS_SELECTED. If the literal is used as an argument + * to a function call in the select-clause, for example, we'd still want to render as a parameter. */ AS_PARAM_OUTSIDE_SELECT( "param-outside-select" ); 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 ee5ff755d9..a00c2a7bcb 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 @@ -12,13 +12,19 @@ import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.TimeZone; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NullPrecedence; @@ -30,6 +36,7 @@ import org.hibernate.grammars.hql.HqlParserBaseVisitor; import org.hibernate.internal.util.collections.Stack; import org.hibernate.internal.util.collections.StandardStack; import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.metamodel.model.domain.BasicDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType; @@ -67,6 +74,7 @@ import org.hibernate.query.sqm.internal.SqmDmlCreationProcessingState; import org.hibernate.query.sqm.internal.SqmQuerySpecCreationProcessingStateStandardImpl; import org.hibernate.query.sqm.spi.ParameterDeclarationContext; import org.hibernate.query.sqm.spi.SqmCreationContext; +import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.SqmTypedNode; @@ -80,7 +88,6 @@ import org.hibernate.query.sqm.tree.domain.SqmMinIndexPath; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; -import org.hibernate.query.sqm.tree.expression.LiteralHelper; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; import org.hibernate.query.sqm.tree.expression.SqmCaseSimple; @@ -89,6 +96,7 @@ import org.hibernate.query.sqm.tree.expression.SqmCollectionSize; import org.hibernate.query.sqm.tree.expression.SqmDistinct; 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.SqmFunction; import org.hibernate.query.sqm.tree.expression.SqmLiteral; import org.hibernate.query.sqm.tree.expression.SqmLiteralNull; @@ -97,6 +105,7 @@ import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.expression.SqmParameterizedEntityType; import org.hibernate.query.sqm.tree.expression.SqmPathEntityType; import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter; +import org.hibernate.query.sqm.tree.expression.SqmSelfRenderingExpression; import org.hibernate.query.sqm.tree.expression.SqmStar; import org.hibernate.query.sqm.tree.expression.SqmTrimSpecification; import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation; @@ -136,7 +145,9 @@ import org.hibernate.query.sqm.tree.select.SqmSelection; import org.hibernate.query.sqm.tree.select.SqmSortSpecification; import org.hibernate.query.sqm.tree.select.SqmSubQuery; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.DateTimeUtils; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.jboss.logging.Logger; @@ -150,6 +161,7 @@ import static org.hibernate.query.TemporalUnit.DAY_OF_MONTH; import static org.hibernate.query.TemporalUnit.DAY_OF_WEEK; import static org.hibernate.query.TemporalUnit.DAY_OF_YEAR; import static org.hibernate.query.TemporalUnit.OFFSET; +import static org.hibernate.query.TemporalUnit.SECOND; import static org.hibernate.query.TemporalUnit.TIME; import static org.hibernate.query.TemporalUnit.TIMEZONE_HOUR; import static org.hibernate.query.TemporalUnit.TIMEZONE_MINUTE; @@ -171,6 +183,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre * Main entry point into analysis of HQL/JPQL parse tree - producing a semantic model of the * query. */ + @SuppressWarnings("WeakerAccess") public static SqmStatement buildSemanticModel( HqlParser.StatementContext hqlParseTree, SqmCreationOptions creationOptions, @@ -195,6 +208,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return processingStateStack; } + @SuppressWarnings("WeakerAccess") public SemanticQueryBuilder(SqmCreationOptions creationOptions, SqmCreationContext creationContext) { this.creationOptions = creationOptions; this.creationContext = creationContext; @@ -1408,7 +1422,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return new SqmFunction( "mod", getFunctionDescriptor( "mod" ), - (AllowableFunctionReturnType) dividend.getNodeType(), + dividend.getNodeType(), asList( dividend, divisor ), creationContext.getNodeBuilder() ); @@ -1513,7 +1527,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return new SqmFunction( "local_datetime", getFunctionDescriptor( "local_datetime" ), - StandardBasicTypes.TIMESTAMP, + StandardBasicTypes.LOCAL_DATE_TIME, creationContext.getNodeBuilder() ); } @@ -1524,7 +1538,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return new SqmFunction( "local_date", getFunctionDescriptor( "local_date" ), - StandardBasicTypes.TIMESTAMP, + StandardBasicTypes.LOCAL_DATE, creationContext.getNodeBuilder() ); } @@ -1535,7 +1549,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return new SqmFunction( "local_time", getFunctionDescriptor( "local_time" ), - StandardBasicTypes.TIMESTAMP, + StandardBasicTypes.LOCAL_TIME, creationContext.getNodeBuilder() ); } @@ -1568,7 +1582,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return new SqmFunction( "least", getFunctionDescriptor( "least" ), - (AllowableFunctionReturnType) type, + type, arguments, creationContext.getNodeBuilder() ); @@ -1637,22 +1651,23 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override public SqmLiteral visitLiteralExpression(HqlParser.LiteralExpressionContext ctx) { - if ( ctx.literal().CHARACTER_LITERAL() != null ) { - return characterLiteral( ctx.literal().CHARACTER_LITERAL().getText() ); - } - else if ( ctx.literal().STRING_LITERAL() != null ) { + if ( ctx.literal().STRING_LITERAL() != null ) { return stringLiteral( ctx.literal().STRING_LITERAL().getText() ); } - else if ( ctx.literal().INTEGER_LITERAL() != null ) { + + if ( ctx.literal().INTEGER_LITERAL() != null ) { return integerLiteral( ctx.literal().INTEGER_LITERAL().getText() ); } - else if ( ctx.literal().LONG_LITERAL() != null ) { + + if ( ctx.literal().LONG_LITERAL() != null ) { return longLiteral( ctx.literal().LONG_LITERAL().getText() ); } - else if ( ctx.literal().BIG_INTEGER_LITERAL() != null ) { + + if ( ctx.literal().BIG_INTEGER_LITERAL() != null ) { return bigIntegerLiteral( ctx.literal().BIG_INTEGER_LITERAL().getText() ); } - else if ( ctx.literal().HEX_LITERAL() != null ) { + + if ( ctx.literal().HEX_LITERAL() != null ) { final String text = ctx.literal().HEX_LITERAL().getText(); if ( text.endsWith( "l" ) || text.endsWith( "L" ) ) { return longLiteral( text ); @@ -1661,7 +1676,8 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return integerLiteral( text ); } } - else if ( ctx.literal().OCTAL_LITERAL() != null ) { + + if ( ctx.literal().OCTAL_LITERAL() != null ) { final String text = ctx.literal().OCTAL_LITERAL().getText(); if ( text.endsWith( "l" ) || text.endsWith( "L" ) ) { return longLiteral( text ); @@ -1670,56 +1686,379 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre return integerLiteral( text ); } } - else if ( ctx.literal().FLOAT_LITERAL() != null ) { + + if ( ctx.literal().FLOAT_LITERAL() != null ) { return floatLiteral( ctx.literal().FLOAT_LITERAL().getText() ); } - else if ( ctx.literal().DOUBLE_LITERAL() != null ) { + + if ( ctx.literal().DOUBLE_LITERAL() != null ) { return doubleLiteral( ctx.literal().DOUBLE_LITERAL().getText() ); } - else if ( ctx.literal().BIG_DECIMAL_LITERAL() != null ) { + + if ( ctx.literal().BIG_DECIMAL_LITERAL() != null ) { return bigDecimalLiteral( ctx.literal().BIG_DECIMAL_LITERAL().getText() ); } - else if ( ctx.literal().FALSE() != null ) { + + if ( ctx.literal().FALSE() != null ) { return booleanLiteral( false ); } - else if ( ctx.literal().TRUE() != null ) { + + if ( ctx.literal().TRUE() != null ) { return booleanLiteral( true ); } - else if ( ctx.literal().NULL() != null ) { + + if ( ctx.literal().NULL() != null ) { return new SqmLiteralNull( creationContext.getQueryEngine().getCriteriaBuilder() ); } - else if ( ctx.literal().timestampLiteral() != null ) { - return LiteralHelper.timestampLiteralFrom( ctx.literal().timestampLiteral().dateTimeLiteralText().getText(), this ); + + if ( ctx.literal().temporalLiteral() != null ) { + return interpretTemporalLiteral( ctx.literal().temporalLiteral() ); } - else if ( ctx.literal().dateLiteral() != null ) { - return LiteralHelper.dateLiteralFrom( ctx.literal().dateLiteral().dateTimeLiteralText().getText(), this ); - } - else if ( ctx.literal().timeLiteral() != null ) { - return LiteralHelper.timeLiteralFrom( ctx.literal().timeLiteral().dateTimeLiteralText().getText(), this ); + + if ( ctx.literal().generalizedLiteral() != null ) { + throw new NotYetImplementedFor6Exception( getClass() ); } // otherwise we have a problem throw new ParsingException( "Unexpected literal expression type [" + ctx.getText() + "]" ); } + private SqmLiteral interpretTemporalLiteral(HqlParser.TemporalLiteralContext temporalLiteral) { + if ( temporalLiteral.dateTimeLiteral() != null ) { + return interpretDateTimeLiteral( temporalLiteral.dateTimeLiteral() ); + } + + if ( temporalLiteral.dateLiteral() != null ) { + return interpretDateLiteral( temporalLiteral.dateLiteral() ); + } + + if ( temporalLiteral.timeLiteral() != null ) { + return interpretTimeLiteral( temporalLiteral.timeLiteral() ); + } + + if ( temporalLiteral.jdbcTimestampLiteral() != null ) { + return interpretJdbcDateTimeLiteral( temporalLiteral.jdbcTimestampLiteral() ); + } + + if ( temporalLiteral.jdbcDateLiteral() != null ) { + return interpretJdbcDateLiteral( temporalLiteral.jdbcDateLiteral() ); + } + + if ( temporalLiteral.jdbcTimeLiteral() != null ) { + return interpretJdbcTimeLiteral( temporalLiteral.jdbcTimeLiteral() ); + } + + // otherwise we have a problem + throw new ParsingException( "Could not interpret temporal literal expression [" + temporalLiteral.getText() + "]" ); + } + + private SqmLiteral interpretDateTimeLiteral(HqlParser.DateTimeLiteralContext dateTimeLiteral) { + final HqlParser.DateTimeContext dateTimeCtx = dateTimeLiteral.dateTime(); + + final HqlParser.DateContext dateCtx = dateTimeCtx.date(); + final HqlParser.TimeContext timeCtx = dateTimeCtx.time(); + + final LocalDate localDate = localDate( dateCtx ); + final LocalTime localTime = localTime( timeCtx ); + + if ( dateTimeCtx.zoneId() == null && dateTimeCtx.offset() == null ) { + return new SqmLiteral<>( + LocalDateTime.of( localDate, localTime ), + basicType( LocalDateTime.class ), + creationContext.getNodeBuilder() + ); + } + + if ( dateTimeCtx.zoneId() != null ) { + // we have a "zone id", which could still identify an offset rather that a ZoneId + try { + //noinspection unchecked + return new SqmLiteral( + ZonedDateTime.of( + localDate, + localTime, + ZoneId.of( dateTimeCtx.zoneId().STRING_LITERAL().getText() ) + ), + basicType( OffsetDateTime.class ), + creationContext.getNodeBuilder() + ); + } + catch (Exception e) { + //noinspection unchecked + return new SqmLiteral( + ZonedDateTime.of( + localDate, + localTime, + TimeZone.getTimeZone( dateTimeCtx.zoneId().STRING_LITERAL().getText() ).toZoneId() + ), + basicType( ZonedDateTime.class ), + creationContext.getNodeBuilder() + ); + } + } + + final HqlParser.OffsetContext offsetCtx = dateTimeCtx.offset(); + assert offsetCtx != null; + + // handle the offset + final int hourCtx = Integer.parseInt( offsetCtx.hour().INTEGER_LITERAL().getText() ); + final int hour = offsetCtx.MINUS() == null + ? hourCtx + : 0 - hourCtx; + final ZoneOffset offset = ZoneOffset.ofHoursMinutes( + hour, + offsetCtx.minute() == null + ? 0 + : Integer.parseInt( offsetCtx.minute().INTEGER_LITERAL().getText() ) + ); + + //noinspection unchecked + return new SqmLiteral( + OffsetDateTime.of( localDate, localTime, offset ), + basicType( OffsetDateTime.class ), + creationContext.getNodeBuilder() + ); + } + + private SqmLiteral interpretJdbcDateTimeLiteral(HqlParser.JdbcTimestampLiteralContext jdbcDateTimeLiteralCtx) { + final Timestamp timestamp; + + if ( jdbcDateTimeLiteralCtx.genericTemporalLiteralText() != null ) { + final TemporalAccessor parsed = DateTimeUtils.DATE_TIME.parseBest( + jdbcDateTimeLiteralCtx.genericTemporalLiteralText().STRING_LITERAL().getText(), + OffsetDateTime::from, + ZonedDateTime::from, + LocalDateTime::from + ); + + if ( parsed instanceof LocalDateTime ) { + final LocalDateTime localDateTime = (LocalDateTime) parsed; + + //noinspection deprecation + timestamp = new Timestamp( + localDateTime.getYear(), + localDateTime.getMonthValue(), + localDateTime.getDayOfMonth(), + localDateTime.getHour(), + localDateTime.getMinute(), + localDateTime.getSecond(), + localDateTime.getNano() + ); + } + else if ( parsed instanceof OffsetDateTime ) { + timestamp = new Timestamp( ( (OffsetDateTime) parsed ).toInstant().toEpochMilli() ); + } + else { + assert parsed instanceof ZonedDateTime; + timestamp = new Timestamp( ( (ZonedDateTime) parsed ).toInstant().toEpochMilli() ); + } + } + else { + final HqlParser.DateTimeContext dateTimeCtx = jdbcDateTimeLiteralCtx.dateTime(); + assert dateTimeCtx != null; + + final LocalDateTime localDateTime = LocalDateTime.of( + localDate( dateTimeCtx.date() ), + localTime( dateTimeCtx.time() ) + ); + + if ( dateTimeCtx.zoneId() == null && dateTimeCtx.offset() == null ) { + // todo (6.0) : use local-tz or jdbc-tz as offset? + // for now, just use UTC + + timestamp = new Timestamp( localDateTime.toInstant( ZoneOffset.UTC ).toEpochMilli() ); + +// below is another option +// timestamp = new Timestamp( +// localDateTime.getYear(), +// localDateTime.getMonthValue(), +// localDateTime.getDayOfMonth(), +// localDateTime.getHour(), +// localDateTime.getMinute(), +// localDateTime.getSecond(), +// localDateTime.getNano() +// ); + } + else if ( dateTimeCtx.zoneId() != null ) { + // we have a "zone id", which could still identify an offset rather that a ZoneId + ZoneId zoneId; + try { + zoneId = ZoneId.of( dateTimeCtx.zoneId().STRING_LITERAL().getText() ); + } + catch (Exception e) { + zoneId = TimeZone.getTimeZone( dateTimeCtx.zoneId().STRING_LITERAL().getText() ).toZoneId(); + //noinspection unchecked + return new SqmLiteral( + ZonedDateTime.of( localDateTime, zoneId ), + basicType( OffsetDateTime.class ), + creationContext.getNodeBuilder() + ); + } + + timestamp = new Timestamp( ZonedDateTime.of( localDateTime, zoneId ).toInstant().toEpochMilli() ); + } + else { + assert dateTimeCtx.offset() != null; + + final HqlParser.OffsetContext offsetCtx = dateTimeCtx.offset(); + assert offsetCtx != null; + + // handle the offset + final int hourCtx = Integer.parseInt( offsetCtx.hour().INTEGER_LITERAL().getText() ); + final int hour = offsetCtx.MINUS() == null + ? hourCtx + : 0 - hourCtx; + final ZoneOffset offset = ZoneOffset.ofHoursMinutes( + hour, + offsetCtx.minute() == null + ? 0 + : Integer.parseInt( offsetCtx.minute().INTEGER_LITERAL().getText() ) + ); + + timestamp = new Timestamp( OffsetDateTime.of( localDateTime, offset ).toInstant().toEpochMilli() ); + } + } + + //noinspection unchecked + return new SqmLiteral( timestamp, basicType( Timestamp.class ), creationContext.getNodeBuilder() ); + } + + private BasicDomainType basicType(Class javaType) { + //noinspection unchecked + return creationContext.getJpaMetamodel().getTypeConfiguration().standardBasicTypeForJavaType( javaType ); + } + + private static LocalDate localDate(HqlParser.DateContext ctx) { + return LocalDate.of( + Integer.parseInt( ctx.year().getText() ), + Integer.parseInt( ctx.month().getText() ), + Integer.parseInt( ctx.day().getText() ) + ); + } + + private static LocalTime localTime(HqlParser.TimeContext ctx) { + if ( ctx.second() != null ) { + int index = ctx.second().getText().indexOf('.'); + if ( index < 0 ) { + return LocalTime.of( + Integer.parseInt( ctx.hour().getText() ), + Integer.parseInt( ctx.minute().getText() ), + Integer.parseInt( ctx.second().getText() ) + ); + } + else { + return LocalTime.of( + Integer.parseInt( ctx.hour().getText() ), + Integer.parseInt( ctx.minute().getText() ), + Integer.parseInt( ctx.second().getText().substring( 0, index ) ), + Integer.parseInt( ctx.second().getText().substring( index + 1 ) ) + ); + } + } + else { + return LocalTime.of( + Integer.parseInt( ctx.hour().getText() ), + Integer.parseInt( ctx.minute().getText() ) + ); + } + } + + private SqmLiteral interpretDateLiteral(HqlParser.DateLiteralContext dateLiteral) { + //noinspection unchecked + return new SqmLiteral( + localDate( dateLiteral.date() ), + basicType( LocalDate.class ), + creationContext.getNodeBuilder() + ); + } + + private SqmLiteral interpretJdbcDateLiteral(HqlParser.JdbcDateLiteralContext dateLiteral) { + if ( dateLiteral.genericTemporalLiteralText() != null ) { + final LocalDate parsed = DateTimeUtils.DATE.parse( + dateLiteral.genericTemporalLiteralText().STRING_LITERAL().getText(), + LocalDate::from + ); + + //noinspection unchecked,deprecation + return new SqmLiteral( + new Date( parsed.getYear(), parsed.getMonthValue(), parsed.getDayOfMonth() ), + basicType( Date.class ), + creationContext.getNodeBuilder() + ); + } + + final HqlParser.DateContext dateCtx = dateLiteral.date(); + + //noinspection unchecked,deprecation + return new SqmLiteral( + new Date( + Integer.parseInt( dateCtx.year().INTEGER_LITERAL().getText() ), + Integer.parseInt( dateCtx.month().INTEGER_LITERAL().getText() ), + Integer.parseInt( dateCtx.day().INTEGER_LITERAL().getText() ) + ), + basicType( Date.class ), + creationContext.getNodeBuilder() + ); + } + + private SqmLiteral interpretTimeLiteral(HqlParser.TimeLiteralContext timeLiteral) { + //noinspection unchecked + return new SqmLiteral( + localTime( timeLiteral.time() ), + basicType( LocalTime.class ), + creationContext.getNodeBuilder() + ); + } + + private SqmLiteral interpretJdbcTimeLiteral(HqlParser.JdbcTimeLiteralContext timeLiteral) { + if ( timeLiteral.genericTemporalLiteralText() != null ) { + final LocalTime parsed = DateTimeUtils.TIME.parse( + timeLiteral.genericTemporalLiteralText().STRING_LITERAL().getText(), + LocalTime::from + ); + + //noinspection unchecked,deprecation + return new SqmLiteral( + new Time( parsed.getHour(), parsed.getMinute(), parsed.getSecond() ), + basicType( Time.class ), + creationContext.getNodeBuilder() + ); + } + + final int seconds; + + if ( timeLiteral.time().second().FLOAT_LITERAL() == null ) { + seconds = Integer.parseInt( timeLiteral.time().second().INTEGER_LITERAL().getText() ); + } + else { + final float floatValue = Float.parseFloat( timeLiteral.time().second().FLOAT_LITERAL().getText() ); + log.debugf( "Float-value encountered for seconds part of JDBC Date literal - ignoring fractional : " + floatValue ); + seconds = (int) floatValue; + } + + //noinspection unchecked,deprecation + return new SqmLiteral( + new Time( + Integer.parseInt( timeLiteral.time().hour().INTEGER_LITERAL().getText() ), + Integer.parseInt( timeLiteral.time().minute().INTEGER_LITERAL().getText() ), + seconds + ), + basicType( Time.class ), + creationContext.getNodeBuilder() + ); + } + + @Override + public SqmLiteral visitGeneralizedLiteral(HqlParser.GeneralizedLiteralContext ctx) { + throw new NotYetImplementedFor6Exception( getClass() ); + } + private SqmLiteral booleanLiteral(boolean value) { final SqmExpressable expressionType = resolveExpressableTypeBasic( Boolean.class ); //noinspection unchecked return new SqmLiteral<>( value, expressionType, creationContext.getQueryEngine().getCriteriaBuilder() ); } - private SqmLiteral characterLiteral(String text) { - if ( text.length() > 1 ) { - // todo : or just treat it as a String literal? - throw new ParsingException( "Value for CHARACTER_LITERAL token was more than 1 character" ); - } - return new SqmLiteral<>( - text.charAt( 0 ), - resolveExpressableTypeBasic( Character.class ), - creationContext.getNodeBuilder() - ); - } - private SqmLiteral stringLiteral(String text) { return new SqmLiteral<>( text, @@ -2208,29 +2547,29 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override public Object visitDayField(HqlParser.DayFieldContext ctx) { - NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); - if (ctx.WEEK()!=null) { - return new SqmExtractUnit<>(DAY_OF_WEEK, resolveExpressableTypeBasic( Integer.class ), nodeBuilder); + final NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); + if ( ctx.WEEK() != null ) { + return new SqmExtractUnit<>( DAY_OF_WEEK, resolveExpressableTypeBasic( Integer.class ), nodeBuilder ); } - if (ctx.MONTH()!=null) { - return new SqmExtractUnit<>(DAY_OF_MONTH, resolveExpressableTypeBasic( Integer.class ), nodeBuilder); + if ( ctx.MONTH() != null ) { + return new SqmExtractUnit<>( DAY_OF_MONTH, resolveExpressableTypeBasic( Integer.class ), nodeBuilder ); } - if (ctx.YEAR()!=null) { - return new SqmExtractUnit<>(DAY_OF_YEAR, resolveExpressableTypeBasic( Integer.class ), nodeBuilder); + if ( ctx.YEAR() != null ) { + return new SqmExtractUnit<>( DAY_OF_YEAR, resolveExpressableTypeBasic( Integer.class ), nodeBuilder ); } return super.visitDayField(ctx); } @Override public Object visitWeekField(HqlParser.WeekFieldContext ctx) { - NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); - if (ctx.MONTH()!=null) { + final NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); + if ( ctx.MONTH() != null ) { //this is computed from DAY_OF_MONTH/7 - return new SqmExtractUnit<>(WEEK_OF_MONTH, resolveExpressableTypeBasic( Integer.class ), nodeBuilder); + return new SqmExtractUnit<>( WEEK_OF_MONTH, resolveExpressableTypeBasic( Integer.class ), nodeBuilder ); } - if (ctx.YEAR()!=null) { + if ( ctx.YEAR() != null ) { //this is computed from DAY_OF_YEAR/7 - return new SqmExtractUnit<>(WEEK_OF_YEAR, resolveExpressableTypeBasic( Integer.class ), nodeBuilder); + return new SqmExtractUnit<>( WEEK_OF_YEAR, resolveExpressableTypeBasic( Integer.class ), nodeBuilder ); } return super.visitWeekField(ctx); } @@ -2256,41 +2595,259 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override public Object visitTimeZoneField(HqlParser.TimeZoneFieldContext ctx) { NodeBuilder nodeBuilder = creationContext.getNodeBuilder(); - if (ctx.HOUR()!=null) { - return new SqmExtractUnit<>(TIMEZONE_HOUR, resolveExpressableTypeBasic( Integer.class ), nodeBuilder); + if ( ctx.HOUR() != null ) { + return new SqmExtractUnit<>( TIMEZONE_HOUR, resolveExpressableTypeBasic( Integer.class ), nodeBuilder ); } - else if (ctx.MINUTE()!=null) { - return new SqmExtractUnit<>(TIMEZONE_MINUTE, resolveExpressableTypeBasic( Integer.class ), nodeBuilder); + else if ( ctx.MINUTE() != null ) { + return new SqmExtractUnit<>( TIMEZONE_MINUTE, resolveExpressableTypeBasic( Integer.class ), nodeBuilder ); } else { - return new SqmExtractUnit<>( OFFSET, resolveExpressableTypeBasic( ZoneOffset.class ), nodeBuilder); + return new SqmExtractUnit<>( OFFSET, resolveExpressableTypeBasic( ZoneOffset.class ), nodeBuilder ); } } private boolean isExtractingJdbcTemporalType; @Override - public Object visitExtractFunction(HqlParser.ExtractFunctionContext ctx) { - final SqmExpression expressionToExtract = (SqmExpression) ctx.expression().accept( this ); - final SqmExtractUnit extractFieldExpression; + public Object visitExtractFunction(HqlParser.ExtractFunctionContext extractFunctionCtx) { + final SqmExpression sqmTemporalExpr = (SqmExpression) extractFunctionCtx.expression().accept( this ); + final SqmExtractUnit sqmExtractUnit; - if ( ctx.extractField() != null ) { - extractFieldExpression = (SqmExtractUnit) ctx.extractField().accept( this); + // Allow `#visitDateOrTimeField()` to know if we're extracting from a JDBC Timestamp or from a + // java.time LocalDateTime/OffsetDateTime + isExtractingJdbcTemporalType = isJdbcTemporalType( sqmTemporalExpr.getNodeType() ); + + if ( extractFunctionCtx.extractField() != null ) { + // for the case of the full ANSI syntax "extract(field from arg)" + sqmExtractUnit = (SqmExtractUnit) extractFunctionCtx.extractField().accept( this); } - else if ( ctx.datetimeField() != null ) { - isExtractingJdbcTemporalType = isJdbcTemporalType( expressionToExtract.getNodeType() ); - extractFieldExpression = (SqmExtractUnit) ctx.datetimeField().accept( this ); + else if ( extractFunctionCtx.datetimeField() != null ) { + // for the shorter legacy Hibernate syntax "field(arg)" + sqmExtractUnit = (SqmExtractUnit) extractFunctionCtx.datetimeField().accept( this ); } else { - return expressionToExtract; + return sqmTemporalExpr; } - //noinspection unchecked - return new SqmFunction( + final TemporalUnit unit = sqmExtractUnit.getTemporalUnit(); + switch ( unit ) { + case NANOSECOND: { + return extractNanoseconds( sqmTemporalExpr ); + } + case OFFSET: { + // use formatdatetime(arg, 'xxx') to get the offset + return extractOffsetUsingFormat( sqmTemporalExpr ); + } + case DATE: + case TIME: { + // use cast(arg as Type) to get the date or time part + // which might be javax.sql.Date / javax.sql.Time or + // java.time.LocalDate / java.time.LocalTime depending + // on the type of the expression we're extracting from + return extractDateOrTimeUsingCast( + sqmTemporalExpr, + sqmExtractUnit.getType() + ); + } + case WEEK_OF_MONTH: { + // use ceiling(extract(day of month, arg)/7.0) + return extractWeek( sqmTemporalExpr, DAY_OF_MONTH ); + } + case WEEK_OF_YEAR: { + // use ceiling(extract(day of year, arg)/7.0) + return extractWeek( sqmTemporalExpr, DAY_OF_YEAR ); + } + default: { + // otherwise it's something we expect the SQL dialect + // itself to understand, either natively, or via the + // registered function template for extract() + + //noinspection unchecked + return new SqmFunction( + "extract", + getFunctionDescriptor( "extract" ), + sqmExtractUnit.getNodeType(), + asList( sqmExtractUnit, sqmTemporalExpr ), + creationContext.getNodeBuilder() + ); + } + } + } + + private SqmExpression extractNanoseconds(SqmExpression sqmTemporalExpr) { + final SqmFunctionDescriptor roundFunctionDescriptor = getFunctionDescriptor( "round" ); + final SqmFunctionDescriptor extractFunctionDescriptor = getFunctionDescriptor( "extract" ); + + final SqmLiteral nanoMultiplierLiteral = floatLiteral( "1e9" ); + final SqmLiteral zeroLiteral = integerLiteral( "0" ); + + final SqmSelfRenderingExpression sqmExtractSeconds = new SqmSelfRenderingExpression<>( + semanticQueryWalker -> extractFunctionDescriptor.generateSqlExpression( + "extract", + asList( + new SqmExtractUnit<>( + SECOND, + StandardBasicTypes.INTEGER, + creationContext.getNodeBuilder() + ), + sqmTemporalExpr + ), + () -> (MappingModelExpressable) basicType( Long.class ), + (SqmToSqlAstConverter) semanticQueryWalker, + (SqlAstCreationState) semanticQueryWalker + ), + basicType( Long.class ), + creationContext.getNodeBuilder() + ); + + return new SqmSelfRenderingExpression<>( + semanticQueryWalker -> roundFunctionDescriptor.generateSqlExpression( + "round", + asList( + new SqmBinaryArithmetic<>( + BinaryArithmeticOperator.MULTIPLY, + sqmExtractSeconds, + nanoMultiplierLiteral, + basicType( Float.class ), + creationContext.getNodeBuilder() + ), + zeroLiteral + ), + () -> (MappingModelExpressable) basicType( Float.class ), + (SqmToSqlAstConverter) semanticQueryWalker, + (SqlAstCreationState) semanticQueryWalker + ), + basicType( Long.class ), + creationContext.getNodeBuilder() + ); + } + + private SqmExpression extractDateOrTimeUsingCast( + SqmExpression expressionToExtract, + AllowableFunctionReturnType type) { + return new SqmFunction<>( + "cast", + getFunctionDescriptor( "cast" ), + type, + asList( + expressionToExtract, + new SqmCastTarget<>( + type, + creationContext.getNodeBuilder() + ) + ), + creationContext.getNodeBuilder() + ); + } + + private SqmExpression extractOffsetUsingFormat(SqmExpression expressionToExtract) { + return new SqmFunction<>( + "formatdatetime", + getFunctionDescriptor( "formatdatetime" ), + basicType( ZoneOffset.class ), + asList( + expressionToExtract, + new SqmFormat( + "xxx", //pattern for timezone offset + basicType( String.class ), + creationContext.getNodeBuilder() + ) + ), + creationContext.getNodeBuilder() + ); + } + + private SqmExpression extractWeek( + SqmExpression expressionToExtract, + TemporalUnit dayOf) { + final BasicDomainType intType = basicType( Integer.class ); + + final SqmFunctionDescriptor extractFunctionDescriptor = getFunctionDescriptor( "extract" ); + + final SqmFunction extractDayOf = new SqmFunction<>( "extract", - getFunctionDescriptor( "extract" ), - extractFieldExpression.getNodeType(), - asList( extractFieldExpression, expressionToExtract ), + extractFunctionDescriptor, + intType, + asList( + new SqmExtractUnit<>( + dayOf, + intType, + creationContext.getNodeBuilder() + ), + expressionToExtract + ), + creationContext.getNodeBuilder() + ); + + final SqmFunction extractDayOfWeek = new SqmFunction<>( + "extract", + extractFunctionDescriptor, + intType, + asList( + new SqmExtractUnit<>( + DAY_OF_WEEK, + intType, + creationContext.getNodeBuilder() + ), + expressionToExtract + ), + creationContext.getNodeBuilder() + ); + + final SqmBinaryArithmetic subtraction = new SqmBinaryArithmetic<>( + BinaryArithmeticOperator.SUBTRACT, + extractDayOf, + extractDayOfWeek, + intType, + creationContext.getNodeBuilder() + ); + + final SqmBinaryArithmetic division = new SqmBinaryArithmetic<>( + BinaryArithmeticOperator.DIVIDE, + subtraction, + floatLiteral( "7.0" ), + basicType( Float.class ), + creationContext.getNodeBuilder() + ); + + return new SqmBinaryArithmetic<>( + BinaryArithmeticOperator.ADD, + new SqmFunction<>( + "ceiling", + getFunctionDescriptor( "ceiling" ), + intType, + Collections.singletonList( division ), + creationContext.getNodeBuilder() + ), + integerLiteral("1"), + intType, + creationContext.getNodeBuilder() + ); + } + + @Override + public SqmFunction visitFormatFunction(HqlParser.FormatFunctionContext ctx) { + final SqmExpression expressionToCast = (SqmExpression) ctx.expression().accept( this ); + final SqmFormat format = visitFormat( ctx.format() ); + + return new SqmFunction<>( + "formatdatetime", + getFunctionDescriptor( "formatdatetime" ), + basicType( String.class ), + asList( expressionToCast, format ), + creationContext.getNodeBuilder() + ); + } + + @Override + public SqmFormat visitFormat(HqlParser.FormatContext ctx) { + final String formatPattern = ctx.STRING_LITERAL().getText(); +// if (!FORMAT.matcher(format).matches()) { +// throw new SemanticException("illegal format pattern: '" + format + "'"); +// } + return new SqmFormat( + formatPattern, + basicType( String.class ), creationContext.getNodeBuilder() ); } @@ -2633,20 +3190,6 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre @Override public SqmLiteral visitTrimCharacter(HqlParser.TrimCharacterContext ctx) { - // todo (6.0) : we should delay this until we are walking the SQM - - if ( ctx.CHARACTER_LITERAL() != null ) { - final String trimCharText = ctx.CHARACTER_LITERAL().getText(); - if ( trimCharText.length() != 1 ) { - throw new SemanticException( "Expecting [trim character] for TRIM function to be single character, found : " + trimCharText ); - } - return new SqmLiteral<>( - trimCharText.charAt( 0 ), - resolveExpressableTypeBasic( Character.class ), - creationContext.getNodeBuilder() - ); - } - if ( ctx.STRING_LITERAL() != null ) { final String trimCharText = ctx.STRING_LITERAL().getText(); if ( trimCharText.length() != 1 ) { @@ -2971,6 +3514,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre } } + //noinspection unchecked SqmPath result = attribute.getElementPathSource().createSqmPath( pluralAttributePath, this diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 04759d6b07..f5791058f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -69,9 +69,11 @@ import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExtractUnit; import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral; +import org.hibernate.query.sqm.tree.expression.SqmFormat; import org.hibernate.query.sqm.tree.expression.SqmFunction; import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmLiteral; +import org.hibernate.query.sqm.tree.expression.SqmLiteralNull; import org.hibernate.query.sqm.tree.expression.SqmNamedParameter; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter; @@ -125,6 +127,7 @@ import org.hibernate.sql.ast.tree.expression.CastTarget; import org.hibernate.sql.ast.tree.expression.Distinct; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.ExtractUnit; +import org.hibernate.sql.ast.tree.expression.Format; import org.hibernate.sql.ast.tree.expression.QueryLiteral; import org.hibernate.sql.ast.tree.expression.Star; import org.hibernate.sql.ast.tree.expression.TrimSpecification; @@ -781,6 +784,10 @@ public abstract class BaseSqmToSqlAstConverter @Override public Expression visitLiteral(SqmLiteral literal) { + if ( literal instanceof SqmLiteralNull ) { + return new QueryLiteral( null, (BasicValuedMapping) inferableTypeAccessStack.getCurrent().get() ); + } + return new QueryLiteral( literal.getLiteralValue(), (BasicValuedMapping) SqmMappingModelHelper.resolveMappingModelExpressable( @@ -939,13 +946,11 @@ public abstract class BaseSqmToSqlAstConverter @Override public Expression visitFunction(SqmFunction sqmFunction) { - final SqmFunctionDescriptor functionDescriptor = creationContext.getSessionFactory() - .getQueryEngine() - .getSqmFunctionRegistry() - .getFunctionDescriptor( sqmFunction.getFunctionName() ); + final SqmFunctionDescriptor functionDescriptor = sqmFunction.getFunctionDescriptor(); shallownessStack.push( Shallowness.FUNCTION ); try { + //noinspection unchecked return functionDescriptor.generateSqlExpression( sqmFunction.getFunctionName(), sqmFunction.getArguments(), @@ -1009,6 +1014,14 @@ public abstract class BaseSqmToSqlAstConverter } } + @Override + public Object visitFormat(SqmFormat sqmFormat) { + return new Format( + sqmFormat.getLiteralValue(), + (BasicValuedMapping) sqmFormat.getNodeType() + ); + } + // @Override // public Object visitAbsFunction(SqmAbsFunction function) { // shallownessStack.push( Shallowness.FUNCTION ); @@ -1425,7 +1438,7 @@ public abstract class BaseSqmToSqlAstConverter return new BinaryArithmeticExpression( (Expression) expression.getLeftHandOperand().accept( this ), interpret( expression.getOperator() ), (Expression) expression.getRightHandOperand().accept( this ), - determineValueMapping( expression ) + (BasicValuedMapping) determineValueMapping( expression ) ); } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java index ef770d10df..8ee1eac50e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java @@ -13,13 +13,13 @@ import java.util.function.Consumer; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; -import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath; -import org.hibernate.query.sqm.tree.domain.SqmPath; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; -import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.SqlSelectionExpression; @@ -100,13 +100,11 @@ public class BasicValuedPathInterpretation implements AssignableSqmPathInterp assert tableGroup != null; this.tableGroup = tableGroup; - - } @Override - public SqmPath getInterpretedSqmPath() { - return sqmPath; + public NavigablePath getNavigablePath() { + return sqmPath.getNavigablePath(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java index 17c1f0cbfe..3485e9ccf7 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java @@ -13,6 +13,7 @@ import java.util.function.Consumer; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; @@ -78,8 +79,8 @@ public class EmbeddableValuedPathInterpretation implements AssignableSqmPathI } @Override - public SqmPath getInterpretedSqmPath() { - return sqmPath; + public NavigablePath getNavigablePath() { + return sqmPath.getNavigablePath(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java index a9ca3c9321..0b7380189f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java @@ -8,6 +8,7 @@ package org.hibernate.query.sqm.sql.internal; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.sql.ast.spi.SqlAstCreationState; @@ -62,13 +63,13 @@ public class EntityValuedPathInterpretation implements SqmPathInterpretation< } @Override - public SqmPath getInterpretedSqmPath() { - return sqmPath; + public NavigablePath getNavigablePath() { + return sqmPath.getNavigablePath(); } @Override public ModelPart getExpressionType() { - return null; + return mapping; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmPathInterpretation.java index 33c6e6c2fb..c4bc4337f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqmPathInterpretation.java @@ -16,16 +16,11 @@ import org.hibernate.sql.ast.tree.expression.Expression; * for path interpretations because it can (and likely) contains multiple SqlExpressions (entity to its columns, e.g.) * * @see org.hibernate.query.sqm.sql.SqmToSqlAstConverter - * @see #getInterpretedSqmPath * * @author Steve Ebersole */ public interface SqmPathInterpretation extends Expression, DomainResultProducer { - default NavigablePath getNavigablePath() { - return getInterpretedSqmPath().getNavigablePath(); - } - - SqmPath getInterpretedSqmPath(); + NavigablePath getNavigablePath(); @Override ModelPart getExpressionType(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java index beee1b1e58..781a140e8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmFunction.java @@ -55,6 +55,10 @@ public class SqmFunction extends AbstractSqmExpression implements JpaFunct this.arguments = arguments; } + public SqmFunctionDescriptor getFunctionDescriptor() { + return functionDescriptor; + } + @Override public String getFunctionName() { return functionName; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java new file mode 100644 index 0000000000..33ecb69e73 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/expression/SqmSelfRenderingExpression.java @@ -0,0 +1,35 @@ +/* + * 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.query.sqm.tree.expression; + +import java.util.function.Function; + +import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; +import org.hibernate.query.sqm.SqmExpressable; +import org.hibernate.sql.ast.tree.expression.Expression; + +/** + * @author Steve Ebersole + */ +public class SqmSelfRenderingExpression extends AbstractSqmExpression implements SqmExpression { + private final Function renderer; + + public SqmSelfRenderingExpression( + Function renderer, + SqmExpressable type, + NodeBuilder criteriaBuilder) { + super( type, criteriaBuilder ); + this.renderer = renderer; + } + + @Override + public X accept(SemanticQueryWalker walker) { + //noinspection unchecked + return (X) renderer.apply( walker ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelection.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelection.java index da120340c2..4eb24dfbd0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelection.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelection.java @@ -23,6 +23,8 @@ public class SqmSelection extends AbstractSqmNode implements SqmAliasedNode selectableNode, NodeBuilder nodeBuilder) { super( nodeBuilder ); + + assert selectableNode != null; this.selectableNode = selectableNode; } @@ -31,6 +33,8 @@ public class SqmSelection extends AbstractSqmNode implements SqmAliasedNode getTableGroupJoins() { return Collections.emptySet(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/BinaryArithmeticExpression.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/BinaryArithmeticExpression.java index 937cbf3ed9..8538de0ab9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/BinaryArithmeticExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/BinaryArithmeticExpression.java @@ -6,10 +6,17 @@ */ package org.hibernate.sql.ast.tree.expression; -import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.query.BinaryArithmeticOperator; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlSelection; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; /** * @author Steve Ebersole @@ -20,13 +27,13 @@ public class BinaryArithmeticExpression implements Expression, DomainResultProdu private final BinaryArithmeticOperator operator; private final Expression rhsOperand; - private final MappingModelExpressable resultType; + private final BasicValuedMapping resultType; public BinaryArithmeticExpression( Expression lhsOperand, BinaryArithmeticOperator operator, Expression rhsOperand, - MappingModelExpressable resultType) { + BasicValuedMapping resultType) { this.operator = operator; this.lhsOperand = lhsOperand; this.rhsOperand = rhsOperand; @@ -34,7 +41,7 @@ public class BinaryArithmeticExpression implements Expression, DomainResultProdu } @Override - public MappingModelExpressable getExpressionType() { + public BasicValuedMapping getExpressionType() { return resultType; } @@ -43,22 +50,37 @@ public class BinaryArithmeticExpression implements Expression, DomainResultProdu walker.visitBinaryArithmeticExpression( this ); } -// @Override -// public DomainResult createDomainResult( -// String resultVariable, -// DomainResultCreationState creationState) { -// final SqlSelection sqlSelection = creationState.getSqlExpressionResolver().resolveSqlSelection( -// this, -// getType().getJavaTypeDescriptor(), -// creationState.getSqlAstCreationState().getCreationContext().getDomainModel().getTypeConfiguration() -// ); -// //noinspection unchecked -// return new ScalarDomainResultImpl( -// sqlSelection.getValuesArrayPosition(), -// resultVariable, -// resultType.getJavaTypeDescriptor() -// ); -// } + @Override + public DomainResult createDomainResult( + String resultVariable, + DomainResultCreationState creationState) { + final SqlSelection sqlSelection = creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection( + this, + resultType.getBasicType().getJavaTypeDescriptor(), + creationState.getSqlAstCreationState().getCreationContext().getDomainModel().getTypeConfiguration() + ); + + //noinspection unchecked + return new BasicResult( + sqlSelection.getValuesArrayPosition(), + resultVariable, + resultType.getBasicType().getJavaTypeDescriptor() + ); + } + + @Override + public SqlSelection createSqlSelection( + int jdbcPosition, + int valuesArrayPosition, + JavaTypeDescriptor javaTypeDescriptor, + TypeConfiguration typeConfiguration) { + return new SqlSelectionImpl( + jdbcPosition, + valuesArrayPosition, + this, + resultType.getJdbcMapping() + ); + } /** * Get the left-hand operand. diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractTableGroup.java index 4b6f8637b6..6b9c58b66e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractTableGroup.java @@ -9,11 +9,11 @@ package org.hibernate.sql.ast.tree.from; import java.util.Collections; import java.util.HashSet; import java.util.Set; -import java.util.function.BiFunction; import java.util.function.Consumer; import org.hibernate.LockMode; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.spi.SqlAliasBase; @@ -21,7 +21,6 @@ import org.hibernate.sql.ast.spi.SqlAliasBase; * @author Steve Ebersole */ public abstract class AbstractTableGroup extends AbstractColumnReferenceQualifier implements TableGroup { - private final NavigablePath navigablePath; private final TableGroupProducer producer; private final LockMode lockMode; @@ -78,6 +77,11 @@ public abstract class AbstractTableGroup extends AbstractColumnReferenceQualifie return producer; } + @Override + public ModelPart getExpressionType() { + return getModelPart(); + } + @Override public LockMode getLockMode() { return lockMode; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/CompositeTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/CompositeTableGroup.java index 7e4f7e65a8..f3c82f528a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/CompositeTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/CompositeTableGroup.java @@ -15,6 +15,7 @@ import java.util.function.Supplier; import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.query.NavigablePath; @@ -43,6 +44,11 @@ public class CompositeTableGroup implements VirtualTableGroup { return navigablePath; } + @Override + public EmbeddableValuedModelPart getExpressionType() { + return getModelPart(); + } + @Override public String getGroupAlias() { // none, although we could also delegate to the underlyingTableGroup's group-alias @@ -50,7 +56,7 @@ public class CompositeTableGroup implements VirtualTableGroup { } @Override - public ModelPartContainer getModelPart() { + public EmbeddableValuedModelPart getModelPart() { return compositionMapping; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MutatingTableReferenceGroupWrapper.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MutatingTableReferenceGroupWrapper.java index 8f8be573eb..b87b6a64a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MutatingTableReferenceGroupWrapper.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MutatingTableReferenceGroupWrapper.java @@ -13,6 +13,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; import org.hibernate.LockMode; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.query.NavigablePath; @@ -41,6 +42,11 @@ public class MutatingTableReferenceGroupWrapper implements VirtualTableGroup { return navigablePath; } + @Override + public ModelPart getExpressionType() { + return getModelPart(); + } + @Override public String getGroupAlias() { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java index 492dd327d2..3c1848f1e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroup.java @@ -14,6 +14,7 @@ import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.sql.internal.DomainResultProducer; +import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.expression.ColumnReference; @@ -26,7 +27,7 @@ import org.hibernate.sql.results.graph.DomainResultCreationState; * * @author Steve Ebersole */ -public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, DomainResultProducer { +public interface TableGroup extends SqlAstNode, ColumnReferenceQualifier, SqmPathInterpretation, DomainResultProducer { NavigablePath getNavigablePath(); /** diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableGroup.java index 5c080b12d5..932fd22921 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableGroup.java @@ -13,6 +13,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; import org.hibernate.LockMode; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.persister.entity.UnionSubclassEntityPersister; import org.hibernate.query.NavigablePath; @@ -40,6 +41,11 @@ public class UnionTableGroup implements VirtualTableGroup { return navigablePath; } + @Override + public ModelPart getExpressionType() { + return getModelPart(); + } + @Override public String getGroupAlias() { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/EntityCollectionPartTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/EntityCollectionPartTableGroup.java index ec046c180e..89d486f2c6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/EntityCollectionPartTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/collection/internal/EntityCollectionPartTableGroup.java @@ -12,6 +12,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; import org.hibernate.LockMode; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.SqlAstWalker; @@ -42,6 +43,11 @@ public class EntityCollectionPartTableGroup implements TableGroup { return collectionPartPath; } + @Override + public ModelPart getExpressionType() { + return getModelPart(); + } + @Override public String getGroupAlias() { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java index 2f1e08c947..38a9c0d46c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/DateTimeUtils.java @@ -7,10 +7,16 @@ package org.hibernate.type.descriptor; import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; import java.util.Locale; import java.util.TimeZone; @@ -61,6 +67,16 @@ public final class DateTimeUtils { .optionalStart().appendZoneOrOffsetId().optionalEnd() .toFormatter(); + /** + * Pattern used for parsing literal dates in HQL. + */ + public static final DateTimeFormatter DATE = ISO_LOCAL_DATE; + + /** + * Pattern used for parsing literal times in HQL. + */ + public static final DateTimeFormatter TIME = ISO_LOCAL_TIME; + /** * Pattern used for parsing literal offset datetimes in HQL. * @@ -170,4 +186,79 @@ public final class DateTimeUtils { return formatter; } + public static void main(String... args) { + final ZoneId localTzId = ZoneId.systemDefault(); + System.out.printf( "Local tz : %s\n", localTzId ); + + final String[] values = new String[] { + "1999-12-31 12:59:59.3", + "1999-12-31 12:59:59 +02:00", + "1999-12-31 12:59:59 UTC", + "1999-12-31 12:59:59 UTC+02:00", + "1999-12-31 12:59:59 " + localTzId.getId() + }; + + for ( String value : values ) { + final TemporalAccessor parsed = DATE_TIME.parseBest( + value, + OffsetDateTime::from, + ZonedDateTime::from, + LocalDateTime::from + ); + + System.out.println( value + " -> " + parsed + " (" + parsed.getClass().getName() + ")" ); + + final ZonedDateTime zdt; + + if ( parsed instanceof LocalDateTime ) { + // here, "localTzId" would come from the "jdbc timezone" setting? + zdt = ( (LocalDateTime) parsed ).atZone( localTzId ); + System.out.println( " - LocalDateTime adjusted to ZonedDateTime : " + parsed ); + } + else if ( parsed instanceof OffsetDateTime ) { + zdt = ( (OffsetDateTime) parsed ).toZonedDateTime(); + System.out.println( " - OffsetDateTime adjusted to ZonedDateTime : " + parsed ); + } + else { + zdt = (ZonedDateTime) parsed; + } + + System.out.println( " - ZoneId = " + zdt.getZone().getId() ); + System.out.println( " - offset = " + zdt.getOffset() ); + System.out.println( " - normalized = " + zdt.getZone().normalized() ); + + final ZonedDateTime adjusted = zdt.withZoneSameInstant( localTzId ); + System.out.println( " - adjusted = " + adjusted.toLocalDateTime() + " (zone-id:" + adjusted.getZone() + ")" ); + } + } + + public static TemporalAccessor transform(String text) { + return DATE_TIME.parse( + text, + (temporal) -> { + // see if there is an offset or tz + final ZoneId zoneOrOffset = temporal.query( TemporalQueries.zone() ); + if ( zoneOrOffset != null ) { + final ZonedDateTime zdt = ZonedDateTime.from( temporal ); + // EDIT: call normalized() to convert a ZoneId + // with constant offset, e.g., UTC+02:00, to ZoneOffset + if ( zoneOrOffset.normalized() instanceof ZoneOffset ) { + return zdt.toOffsetDateTime(); + } + else { + return zdt; + } + } + + // otherwise it's a LocalDateTime + return LocalDateTime.from( temporal ); + } + ); + } + + public static OffsetDateTime usingOffset(String text) { + // does not work + final TemporalAccessor parsed = DATE_TIME.parse( text ); + return OffsetDateTime.from( parsed ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java index ee1dd6719f..2ea7832ec8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java @@ -15,7 +15,10 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterNumericData; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#BIGINT BIGINT} handling. @@ -38,6 +41,17 @@ public class BigIntTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Long.class ); + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + //noinspection unchecked + return new JdbcLiteralFormatterNumericData( javaTypeDescriptor, Long.class ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java index ff433e8d52..16e88bfe7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java @@ -15,7 +15,9 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#BIT BIT} handling. @@ -40,6 +42,11 @@ public class BitTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Boolean.class ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java index c66c429d8e..3f88ecf434 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java @@ -16,7 +16,9 @@ import java.sql.Types; import org.hibernate.engine.jdbc.BinaryStream; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#BLOB BLOB} handling. @@ -40,6 +42,11 @@ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Blob.class ); + } + @Override public ValueExtractor getExtractor(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicExtractor( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java index 94623d4542..e38f96c1c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java @@ -15,7 +15,10 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterBoolean; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link java.sql.Types#BOOLEAN BOOLEAN} handling. @@ -37,6 +40,17 @@ public class BooleanTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Boolean.class ); + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + //noinspection unchecked + return new JdbcLiteralFormatterBoolean( javaTypeDescriptor ); + } + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java index 33497e7ae1..e968e24cec 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java @@ -14,10 +14,15 @@ import java.sql.SQLException; import java.sql.Types; import java.util.Calendar; +import javax.persistence.TemporalType; + import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterTemporal; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#DATE DATE} handling. @@ -40,6 +45,17 @@ public class DateTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Date.class ); + } + + @Override + @SuppressWarnings("unchecked") + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + return new JdbcLiteralFormatterTemporal( javaTypeDescriptor, TemporalType.DATE ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java index 560c228569..b9139f3b9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java @@ -16,7 +16,10 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterNumericData; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#DECIMAL DECIMAL} handling. @@ -39,6 +42,17 @@ public class DecimalTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( BigDecimal.class ); + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + //noinspection unchecked + return new JdbcLiteralFormatterNumericData( javaTypeDescriptor, BigDecimal.class ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java index e79eaab297..065c9ecf66 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java @@ -15,7 +15,10 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterNumericData; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#DOUBLE DOUBLE} handling. @@ -38,6 +41,17 @@ public class DoubleTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Double.class ); + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + //noinspection unchecked + return new JdbcLiteralFormatterNumericData( javaTypeDescriptor, Double.class ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/FloatTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/FloatTypeDescriptor.java index 3ef4fcbb8f..f022dfc749 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/FloatTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/FloatTypeDescriptor.java @@ -8,6 +8,9 @@ package org.hibernate.type.descriptor.sql; import java.sql.Types; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; +import org.hibernate.type.spi.TypeConfiguration; + /** * Descriptor for {@link Types#FLOAT FLOAT} handling. * @@ -19,6 +22,11 @@ public class FloatTypeDescriptor extends RealTypeDescriptor { public FloatTypeDescriptor() { } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Double.class ); + } + @Override public int getSqlType() { return Types.FLOAT; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java index b1059d773c..260ca6be2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java @@ -15,7 +15,10 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterNumericData; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#INTEGER INTEGER} handling. @@ -38,6 +41,17 @@ public class IntegerTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class ); + } + + @Override + @SuppressWarnings("unchecked") + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + return new JdbcLiteralFormatterNumericData( javaTypeDescriptor, Integer.class ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/JdbcLiteralFormatter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcLiteralFormatter.java similarity index 95% rename from hibernate-core/src/main/java/org/hibernate/sql/ast/spi/JdbcLiteralFormatter.java rename to hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcLiteralFormatter.java index 977649d05a..46178eaf0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/JdbcLiteralFormatter.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/JdbcLiteralFormatter.java @@ -4,7 +4,7 @@ * 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.spi; +package org.hibernate.type.descriptor.sql; import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SharedSessionContractImplementor; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NVarcharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NVarcharTypeDescriptor.java index 97ab828980..e588377caf 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NVarcharTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/NVarcharTypeDescriptor.java @@ -12,13 +12,15 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; -import org.hibernate.sql.ast.spi.JdbcLiteralFormatter; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterCharacterData; +import org.hibernate.type.spi.TypeConfiguration; -import static org.hibernate.sql.ast.spi.JdbcLiteralFormatter.NULL; +import static org.hibernate.type.descriptor.sql.JdbcLiteralFormatter.NULL; /** * Descriptor for {@link Types#NVARCHAR NVARCHAR} handling. @@ -41,9 +43,15 @@ public class NVarcharTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( String.class ); + } + @Override public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { - return (value, dialect, session) -> value == null ? NULL : "'" + value.toString() + "'"; + //noinspection unchecked + return new JdbcLiteralFormatterCharacterData( javaTypeDescriptor, true ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java index 87dfd009a5..26985bed0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java @@ -15,7 +15,10 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterNumericData; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#REAL REAL} handling. @@ -38,6 +41,17 @@ public class RealTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Float.class ); + } + + @Override + @SuppressWarnings("unchecked") + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + return new JdbcLiteralFormatterNumericData( javaTypeDescriptor, Float.class ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java index e787dc1fe7..130e245314 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java @@ -15,7 +15,10 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterNumericData; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#SMALLINT SMALLINT} handling. @@ -38,6 +41,17 @@ public class SmallIntTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Short.class ); + } + + @Override + @SuppressWarnings("unchecked") + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + return new JdbcLiteralFormatterNumericData( javaTypeDescriptor, Short.class ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java index 065351f2ab..93ee2fe424 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SqlTypeDescriptor.java @@ -8,7 +8,6 @@ package org.hibernate.type.descriptor.sql; import java.io.Serializable; -import org.hibernate.sql.ast.spi.JdbcLiteralFormatter; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.java.BasicJavaDescriptor; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java index 3533737ebc..f736fc8627 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java @@ -14,10 +14,15 @@ import java.sql.Time; import java.sql.Types; import java.util.Calendar; +import javax.persistence.TemporalType; + import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterTemporal; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#TIME TIME} handling. @@ -40,6 +45,17 @@ public class TimeTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Time.class ); + } + + @Override + @SuppressWarnings("unchecked") + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + return new JdbcLiteralFormatterTemporal( javaTypeDescriptor, TemporalType.TIME ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java index d37650411a..3ec475bb39 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java @@ -14,10 +14,15 @@ import java.sql.Timestamp; import java.sql.Types; import java.util.Calendar; +import javax.persistence.TemporalType; + import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterTemporal; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#TIMESTAMP TIMESTAMP} handling. @@ -40,6 +45,17 @@ public class TimestampTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Timestamp.class ); + } + + @Override + @SuppressWarnings("unchecked") + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + return new JdbcLiteralFormatterTemporal( javaTypeDescriptor, TemporalType.TIMESTAMP ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java index 7c52a64458..ae42b7be0c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java @@ -15,7 +15,10 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterNumericData; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#TINYINT TINYINT} handling. @@ -41,6 +44,17 @@ public class TinyIntTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Short.class ); + } + + @Override + @SuppressWarnings("unchecked") + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + return new JdbcLiteralFormatterNumericData( javaTypeDescriptor, Short.class ); + } + @Override public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java index d94fbeabe0..510e961ef7 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java @@ -15,7 +15,9 @@ import java.sql.Types; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#VARBINARY VARBINARY} handling. @@ -37,6 +39,11 @@ public class VarbinaryTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( byte[].class ); + } + public ValueBinder getBinder(final JavaTypeDescriptor javaTypeDescriptor) { return new BasicBinder( javaTypeDescriptor, this ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java index 3f0a5be766..d62c40dbaf 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java @@ -12,13 +12,13 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; -import org.hibernate.sql.ast.spi.JdbcLiteralFormatter; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.BasicJavaDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; - -import static org.hibernate.sql.ast.spi.JdbcLiteralFormatter.NULL; +import org.hibernate.type.descriptor.sql.internal.JdbcLiteralFormatterCharacterData; +import org.hibernate.type.spi.TypeConfiguration; /** * Descriptor for {@link Types#VARCHAR VARCHAR} handling. @@ -41,9 +41,15 @@ public class VarcharTypeDescriptor implements SqlTypeDescriptor { return true; } + @Override + public BasicJavaDescriptor getJdbcRecommendedJavaTypeMapping(TypeConfiguration typeConfiguration) { + return (BasicJavaDescriptor) typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( String.class ); + } + @Override public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { - return (value, dialect, session) -> value == null ? NULL : "'" + value.toString() + "'"; + //noinspection unchecked + return new JdbcLiteralFormatterCharacterData( javaTypeDescriptor ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterBoolean.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterBoolean.java new file mode 100644 index 0000000000..5d81347d7f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterBoolean.java @@ -0,0 +1,28 @@ +/* + * 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.type.descriptor.sql.internal; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.spi.BasicJdbcLiteralFormatter; + +/** + * JdbcLiteralFormatter implementation for handling boolean literals + * + * @author Steve Ebersole + */ +public class JdbcLiteralFormatterBoolean extends BasicJdbcLiteralFormatter { + public JdbcLiteralFormatterBoolean(JavaTypeDescriptor javaTypeDescriptor) { + super( javaTypeDescriptor ); + } + + @Override + public String toJdbcLiteral(Object value, Dialect dialect, SharedSessionContractImplementor session) { + return unwrap( value, Boolean.class, session ).toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterCharacterData.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterCharacterData.java new file mode 100644 index 0000000000..132d51ac21 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterCharacterData.java @@ -0,0 +1,45 @@ +/* + * 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.type.descriptor.sql.internal; + +import java.util.Locale; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.spi.BasicJdbcLiteralFormatter; + +/** + * JdbcLiteralFormatter implementation for handling character data + * + * @author Steve Ebersole + */ +public class JdbcLiteralFormatterCharacterData extends BasicJdbcLiteralFormatter { + private final boolean isNationalized; + + public JdbcLiteralFormatterCharacterData(JavaTypeDescriptor javaTypeDescriptor) { + this( javaTypeDescriptor, false ); + } + + public JdbcLiteralFormatterCharacterData(JavaTypeDescriptor javaTypeDescriptor, boolean isNationalized) { + super( javaTypeDescriptor ); + this.isNationalized = isNationalized; + } + + @Override + public String toJdbcLiteral(Object value, Dialect dialect, SharedSessionContractImplementor session) { + final String literalValue = unwrap( value, String.class, session ); + + if ( isNationalized ) { + // is there a standardized form for n-string literals? This is the SQL Server syntax for sure + return String.format( Locale.ROOT, "n'%s'", literalValue ); + } + else { + return String.format( Locale.ROOT, "'%s'", literalValue ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterNumericData.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterNumericData.java new file mode 100644 index 0000000000..ffd4f43a2e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterNumericData.java @@ -0,0 +1,31 @@ +/* + * 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.type.descriptor.sql.internal; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.spi.BasicJdbcLiteralFormatter; + +/** + * @author Steve Ebersole + */ +public class JdbcLiteralFormatterNumericData extends BasicJdbcLiteralFormatter { + private final Class unwrapJavaType; + + public JdbcLiteralFormatterNumericData( + JavaTypeDescriptor javaTypeDescriptor, + Class unwrapJavaType) { + super( javaTypeDescriptor ); + this.unwrapJavaType = unwrapJavaType; + } + + @Override + public String toJdbcLiteral(Object value, Dialect dialect, SharedSessionContractImplementor session) { + return unwrap( value, unwrapJavaType, session ).toString(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterTemporal.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterTemporal.java new file mode 100644 index 0000000000..a99baa6377 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/internal/JdbcLiteralFormatterTemporal.java @@ -0,0 +1,71 @@ +/* + * 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.type.descriptor.sql.internal; + +import java.time.temporal.TemporalAccessor; +import javax.persistence.TemporalType; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.spi.BasicJdbcLiteralFormatter; + +/** + * @author Steve Ebersole + */ +public class JdbcLiteralFormatterTemporal extends BasicJdbcLiteralFormatter { + private final TemporalType precision; + + public JdbcLiteralFormatterTemporal(JavaTypeDescriptor javaTypeDescriptor, TemporalType precision) { + super( javaTypeDescriptor ); + this.precision = precision; + } + + @Override + public String toJdbcLiteral(Object value, Dialect dialect, SharedSessionContractImplementor session) { + // for performance reasons, avoid conversions if we can + if ( value instanceof java.util.Date ) { + return dialect.formatDateTimeLiteral( + (java.util.Date) value, + precision + ); + } + else if ( value instanceof java.util.Calendar ) { + return dialect.formatDateTimeLiteral( + (java.util.Calendar) value, + precision + ); + } + else if ( value instanceof TemporalAccessor ) { + return dialect.formatDateTimeLiteral( + (TemporalAccessor) value, + precision + ); + } + + switch ( precision) { + case DATE: { + return dialect.formatDateTimeLiteral( + unwrap( value, java.sql.Date.class, session ), + precision + ); + } + case TIME: { + return dialect.formatDateTimeLiteral( + unwrap( value, java.sql.Time.class, session ), + precision + ); + } + default: { + return dialect.formatDateTimeLiteral( + unwrap( value, java.util.Date.class, session ), + precision + ); + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/AbstractJdbcLiteralFormatter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/AbstractJdbcLiteralFormatter.java new file mode 100644 index 0000000000..f1ea00c829 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/AbstractJdbcLiteralFormatter.java @@ -0,0 +1,27 @@ +/* + * 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.type.descriptor.sql.spi; + +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.JdbcLiteralFormatter; + +/** + * Abstract JdbcLiteralFormatter implementation managing the JavaTypeDescriptor + * + * @author Steve Ebersole + */ +public abstract class AbstractJdbcLiteralFormatter implements JdbcLiteralFormatter { + private final JavaTypeDescriptor javaTypeDescriptor; + + public AbstractJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + this.javaTypeDescriptor = javaTypeDescriptor; + } + + protected JavaTypeDescriptor getJavaTypeDescriptor() { + return javaTypeDescriptor; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/BasicJdbcLiteralFormatter.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/BasicJdbcLiteralFormatter.java new file mode 100644 index 0000000000..5909ef14f0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/spi/BasicJdbcLiteralFormatter.java @@ -0,0 +1,34 @@ +/* + * 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.type.descriptor.sql.spi; + + +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * Support for JdbcLiteralFormatter implementations with a basic implementation of an unwrap method + * + * @author Steve Ebersole + */ +public abstract class BasicJdbcLiteralFormatter extends AbstractJdbcLiteralFormatter { + public BasicJdbcLiteralFormatter(JavaTypeDescriptor javaTypeDescriptor) { + super( javaTypeDescriptor ); + } + + @SuppressWarnings("unchecked") + protected X unwrap(Object value, Class unwrapType, SharedSessionContractImplementor session) { + assert value != null; + + // for performance reasons, avoid conversions if we can + if ( unwrapType.isInstance( value ) ) { + return (X) value; + } + + return (X) getJavaTypeDescriptor().unwrap( value, unwrapType, session ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/LiteralTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/LiteralTests.java index 02e231f751..c84a5c77a6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/LiteralTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/LiteralTests.java @@ -6,35 +6,92 @@ */ package org.hibernate.orm.test.query.hql; -import org.hibernate.boot.MetadataSources; -import org.hibernate.orm.test.query.sqm.BaseSqmUnitTest; -import org.hibernate.query.sqm.tree.select.SqmSelectStatement; - import org.hibernate.testing.orm.domain.StandardDomainModel; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.Test; /** * @author Steve Ebersole */ -public class LiteralTests extends BaseSqmUnitTest { +@SuppressWarnings("WeakerAccess") +@ServiceRegistry +@DomainModel( standardModels = StandardDomainModel.GAMBIT ) +@SessionFactory +public class LiteralTests { - @Override - protected void applyMetadataSources(MetadataSources metadataSources) { - StandardDomainModel.GAMBIT.getDescriptor().applyDomainModel( metadataSources ); + @Test + public void testJdbcTimeLiteral(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "from EntityOfBasics e1 where e1.theTime = {t 12:30:00}" ).list(); + session.createQuery( "from EntityOfBasics e1 where e1.theTime = {t '12:30:00'}" ).list(); + } + ); } @Test - public void testTimestampLiteral() { - final SqmSelectStatement sqm = interpretSelect( "from EntityOfBasics e1 where e1.theTimestamp = {ts '2018-01-01T12:30:00'}" ); + public void testJdbcDateLiteral(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "from EntityOfBasics e1 where e1.theDate = {d 1999-12-31}" ).list(); + session.createQuery( "from EntityOfBasics e1 where e1.theDate = {d '1999-12-31'}" ).list(); + } + ); } @Test - public void testDateLiteral() { - final SqmSelectStatement sqm = interpretSelect( "from EntityOfBasics e1 where e1.theDate = {d '2018-01-01'}" ); + public void testJdbcTimestampLiteral(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "from EntityOfBasics e1 where e1.theDate = {ts 1999-12-31 12:30:00}" ).list(); + session.createQuery( "from EntityOfBasics e1 where e1.theDate = {ts '1999-12-31 12:30:00'}" ).list(); + } + ); } @Test - public void testTimeLiteral() { - final SqmSelectStatement sqm = interpretSelect( "from EntityOfBasics e1 where e1.theTime = {t '12:30:00'}" ); + public void testLocalDateLiteral(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "from EntityOfBasics e1 where e1.theLocalDate = {1999-12-31}" ).list(); + } + ); + } + + @Test + public void testLocalTimeLiteral(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "from EntityOfBasics e1 where e1.theLocalTime = {12:59:59}" ).list(); + } + ); + } + + @Test + public void testDateTimeLiteral(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + // todo (6.0) : removed this difference between the string-literal form and the date-time-field form (with/without 'T') + + session.createQuery( "from EntityOfBasics e1 where e1.theLocalDateTime = {1999-12-31 12:59:59}" ).list(); + + session.createQuery( "from EntityOfBasics e1 where e1.theZonedDateTime = {1999-12-31 12:59:59 +01:00}" ).list(); + + session.createQuery( "from EntityOfBasics e1 where e1.theZonedDateTime = {1999-12-31 12:59:59 CST}" ).list(); + } + ); + } + + @Test + public void isolated(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "from EntityOfBasics e1 where e1.theLocalDateTime = {1999-12-31 12:59:59}" ).list(); + session.createQuery( "from EntityOfBasics e1 where e1.theZonedDateTime = {1999-12-31 12:59:59 +01:00}" ).list(); + } + ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/StandardFunctionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/StandardFunctionTests.java index a17b60a925..2460f60c01 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/StandardFunctionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/StandardFunctionTests.java @@ -140,7 +140,6 @@ public class StandardFunctionTests { } @Test - @FailureExpected public void testCoalesceFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -617,119 +616,175 @@ public class StandardFunctionTests { public void isolated(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select extract(time from local_datetime), extract(date from local_datetime) from EntityOfBasics e") - .list(); + session.createQuery("select extract(time from e.theTimestamp), extract(date from e.theTimestamp) from EntityOfBasics e").list(); + session.createQuery("select extract(time from local_datetime), extract(date from local_datetime) from EntityOfBasics e").list(); } ); } @Test - @FailureExpected +// @FailureExpected public void testExtractFunctionWithAssertions(SessionFactoryScope scope) { scope.inTransaction( session -> { EntityOfBasics entity = new EntityOfBasics(); entity.setId(1); session.save(entity); - session.flush(); - assertThat( - session.createQuery("select extract(week of year from date '2019-01-01') from EntityOfBasics").getResultList().get(0), - is(1) - ); - assertThat( - session.createQuery("select extract(week of year from date '2019-01-05') from EntityOfBasics").getResultList().get(0), - is(1) - ); - assertThat( - session.createQuery("select extract(week of year from date '2019-01-06') from EntityOfBasics").getResultList().get(0), - is(2) - ); - - assertThat( - session.createQuery("select extract(week of month from date '2019-05-01') from EntityOfBasics").getResultList().get(0), - is(1) - ); - assertThat( - session.createQuery("select extract(week of month from date '2019-05-04') from EntityOfBasics").getResultList().get(0), - is(1) - ); - assertThat( - session.createQuery("select extract(week of month from date '2019-05-05') from EntityOfBasics").getResultList().get(0), - is(2) - ); - - assertThat( - session.createQuery("select extract(week from date '2019-05-27') from EntityOfBasics").getResultList().get(0), - is(22) - ); - assertThat( - session.createQuery("select extract(week from date '2019-06-02') from EntityOfBasics").getResultList().get(0), - is(22) - ); - assertThat( - session.createQuery("select extract(week from date '2019-06-03') from EntityOfBasics").getResultList().get(0), - is(23) - ); - - assertThat( - session.createQuery("select extract(day of year from date '2019-05-30') from EntityOfBasics").getResultList().get(0), - is(150) - ); - assertThat( - session.createQuery("select extract(day of month from date '2019-05-27') from EntityOfBasics").getResultList().get(0), - is(27) - ); - - assertThat( - session.createQuery("select extract(day from date '2019-05-31') from EntityOfBasics").getResultList().get(0), - is(31) - ); - assertThat( - session.createQuery("select extract(month from date '2019-05-31') from EntityOfBasics").getResultList().get(0), - is(5) - ); - assertThat( - session.createQuery("select extract(year from date '2019-05-31') from EntityOfBasics").getResultList().get(0), - is(2019) - ); - assertThat( - session.createQuery("select extract(quarter from date '2019-05-31') from EntityOfBasics").getResultList().get(0), - is(2) - ); - - assertThat( - session.createQuery("select extract(day of week from date '2019-05-27') from EntityOfBasics").getResultList().get(0), - is(2) - ); - assertThat( - session.createQuery("select extract(day of week from date '2019-05-31') from EntityOfBasics").getResultList().get(0), - is(6) - ); - - assertThat( - session.createQuery("select extract(second from time '14:12:10') from EntityOfBasics").getResultList().get(0), - is(10f) - ); - assertThat( - session.createQuery("select extract(minute from time '14:12:10') from EntityOfBasics").getResultList().get(0), - is(12) - ); - assertThat( - session.createQuery("select extract(hour from time '14:12:10') from EntityOfBasics").getResultList().get(0), - is(14) - ); - - assertThat( - session.createQuery("select extract(date from current datetime) from EntityOfBasics").getResultList().get(0), - instanceOf( LocalDate.class) - ); - assertThat( - session.createQuery("select extract(time from current datetime) from EntityOfBasics").getResultList().get(0), - instanceOf( LocalTime.class) - ); - session.delete(entity); } ); + + try { + scope.inTransaction( + session -> { + assertThat( + session.createQuery( + "select extract(week of year from {2019-01-01}) from EntityOfBasics b where b.id = 1" ) + .getResultList() + .get( 0 ), + is( 1 ) + ); + assertThat( + session.createQuery( + "select extract(week of year from {2019-01-01}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 1 ) + ); + assertThat( + session.createQuery( + "select extract(week of year from {2019-01-01}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 1 ) + ); + assertThat( + session.createQuery( + "select extract(week of year from {2019-01-01}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 1 ) + ); + + assertThat( + session.createQuery( + "select extract(week of year from {2019-01-05}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 1 ) + ); + + assertThat( + session.createQuery( + "select extract(week of month from {2019-05-01}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 1 ) + ); + + assertThat( + session.createQuery( "select extract(week from {2019-05-27}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 22 ) + ); + + assertThat( + session.createQuery( + "select extract(day of year from {2019-05-30}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 150 ) + ); + assertThat( + session.createQuery( + "select extract(day of month from {2019-05-27}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 27 ) + ); + + assertThat( + session.createQuery( "select extract(day from {2019-05-31}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 31 ) + ); + assertThat( + session.createQuery( "select extract(month from {2019-05-31}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 5 ) + ); + assertThat( + session.createQuery( "select extract(year from {2019-05-31}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 2019 ) + ); + assertThat( + session.createQuery( + "select extract(quarter from {2019-05-31}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 2 ) + ); + + assertThat( + session.createQuery( + "select extract(day of week from {2019-05-27}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 2 ) + ); + assertThat( + session.createQuery( + "select extract(day of week from {2019-05-31}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 6 ) + ); + + assertThat( + session.createQuery( "select extract(second from {14:12:10}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 10f ) + ); + assertThat( + session.createQuery( "select extract(minute from {14:12:10}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 12 ) + ); + assertThat( + session.createQuery( "select extract(hour from {14:12:10}) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + is( 14 ) + ); + + assertThat( + session.createQuery( "select extract(date from local_datetime) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + instanceOf( LocalDate.class ) + ); + assertThat( + session.createQuery( "select extract(time from local_datetime) from EntityOfBasics" ) + .getResultList() + .get( 0 ), + instanceOf( LocalTime.class ) + ); + } + ); + } + finally { + scope.inTransaction( + session -> { + session.createQuery( "delete from EntityOfBasics" ).executeUpdate(); + } + ); + } } @Test @@ -795,14 +850,8 @@ public class StandardFunctionTests { public void testCountFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select count(*) from EntityOfBasics e") - .list(); - session.createQuery("select count(1) from EntityOfBasics e") - .list(); - session.createQuery("select count(e) from EntityOfBasics e") - .list(); - session.createQuery("select count(distinct e) from EntityOfBasics e") - .list(); + session.createQuery("select count(e) from EntityOfBasics e").list(); + session.createQuery("select count(distinct e) from EntityOfBasics e").list(); } ); } @@ -820,7 +869,6 @@ public class StandardFunctionTests { } @Test - @FailureExpected public void testFormat(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -829,25 +877,45 @@ public class StandardFunctionTests { entity.setTheDate( new Date( 74, 2, 25 ) ); entity.setTheTime( new Time( 23, 10, 8 ) ); entity.setTheTimestamp( new Timestamp( System.currentTimeMillis() ) ); - session.persist(entity); - session.flush(); - - session.createQuery("select format(e.theTime as 'hh:mm:ss aa') from EntityOfBasics e") - .list(); - session.createQuery("select format(e.theDate as 'dd/MM/yy'), format(e.theDate as 'EEEE, MMMM dd, yyyy') from EntityOfBasics e") - .list(); - session.createQuery("select format(e.theTimestamp as 'dd/MM/yyyy ''at'' HH:mm:ss') from EntityOfBasics e") - .list(); - - assertThat( - session.createQuery("select format(e.theDate as 'EEEE, dd/MM/yyyy') from EntityOfBasics").getResultList().get(0), - is("Monday, 25/03/1974") - ); - assertThat( - session.createQuery("select format(e.theTime as '''Hello'', hh:mm:ss aa') from EntityOfBasics").getResultList().get(0), - is("Hello, 11:10:08 PM") - ); + session.persist( entity ); } ); + + try { + scope.inTransaction( + session -> { + session.createQuery( "select format(e.theTime as 'hh:mm:ss aa') from EntityOfBasics e" ) + .list(); + session.createQuery( + "select format(e.theDate as 'dd/MM/yy'), format(e.theDate as 'EEEE, MMMM dd, yyyy') from EntityOfBasics e" ) + .list(); + session.createQuery( + "select format(e.theTimestamp as 'dd/MM/yyyy ''at'' HH:mm:ss') from EntityOfBasics e" ) + .list(); + + assertThat( + session.createQuery( + "select format(e.theDate as 'EEEE, dd/MM/yyyy') from EntityOfBasics e" ) + .getResultList() + .get( 0 ), + is( "Monday, 25/03/1974" ) + ); + assertThat( + session.createQuery( + "select format(e.theTime as '''Hello'', hh:mm:ss aa') from EntityOfBasics e" ) + .getResultList() + .get( 0 ), + is( "Hello, 11:10:08 PM" ) + ); + } + ); + } + finally { + scope.inTransaction( + session -> { + session.createQuery( "delete EntityOfBasics" ).executeUpdate(); + } + ); + } } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java index 22ff344911..92cd472b0b 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/EntityOfBasics.java @@ -9,6 +9,11 @@ package org.hibernate.testing.orm.domain.gambit; import java.net.URL; import java.sql.Clob; import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.Date; import javax.persistence.AttributeConverter; import javax.persistence.Column; @@ -42,6 +47,11 @@ public class EntityOfBasics { private Date theTime; private Date theTimestamp; private Instant theInstant; + private LocalDateTime theLocalDateTime; + private LocalDate theLocalDate; + private LocalTime theLocalTime; + private OffsetDateTime theOffsetDateTime; + private ZonedDateTime theZonedDateTime; private Gender gender; private Gender convertedGender; private Gender ordinalGender; @@ -167,6 +177,46 @@ public class EntityOfBasics { this.theInstant = theInstant; } + public LocalDateTime getTheLocalDateTime() { + return theLocalDateTime; + } + + public void setTheLocalDateTime(LocalDateTime theLocalDateTime) { + this.theLocalDateTime = theLocalDateTime; + } + + public LocalDate getTheLocalDate() { + return theLocalDate; + } + + public void setTheLocalDate(LocalDate theLocalDate) { + this.theLocalDate = theLocalDate; + } + + public LocalTime getTheLocalTime() { + return theLocalTime; + } + + public void setTheLocalTime(LocalTime theLocalTime) { + this.theLocalTime = theLocalTime; + } + + public OffsetDateTime getTheOffsetDateTime() { + return theOffsetDateTime; + } + + public void setTheOffsetDateTime(OffsetDateTime theOffsetDateTime) { + this.theOffsetDateTime = theOffsetDateTime; + } + + public ZonedDateTime getTheZonedDateTime() { + return theZonedDateTime; + } + + public void setTheZonedDateTime(ZonedDateTime theZonedDateTime) { + this.theZonedDateTime = theZonedDateTime; + } + public static class GenderConverter implements AttributeConverter { @Override public Character convertToDatabaseColumn(Gender attribute) {