From 9f096e89ec6c5438d23e15dd5f4bd844ade4f922 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 10 Feb 2021 13:03:26 +0100 Subject: [PATCH] Repurpose DefaultSizeStrategy to SizeStrategy for resolving final size. Fix boolean encoding/decoding issues. Remove duplicate order bys. Fix set operation nesting. Fix lots of tests for SQLServer, MariaDB, Derby and Oracle --- docker_db.sh | 4 +- .../org/hibernate/grammars/hql/HqlParser.g4 | 10 +- .../SessionFactoryOptionsBuilder.java | 9 +- .../internal/InferredBasicValueResolver.java | 2 +- .../internal/hbm/RelationalObjectBinder.java | 8 +- ...stractDelegatingSessionFactoryOptions.java | 5 + .../boot/spi/SessionFactoryOptions.java | 2 + .../java/org/hibernate/cfg/Ejb3Column.java | 2 +- .../dialect/AbstractHANADialect.java | 2 +- .../dialect/AbstractTransactSQLDialect.java | 4 + .../org/hibernate/dialect/BooleanDecoder.java | 104 +++++ .../org/hibernate/dialect/CUBRIDDialect.java | 2 +- .../org/hibernate/dialect/CacheDialect.java | 2 +- .../org/hibernate/dialect/DB2Dialect.java | 28 +- .../org/hibernate/dialect/DerbyDialect.java | 66 +-- .../java/org/hibernate/dialect/Dialect.java | 163 +++++-- .../hibernate/dialect/FirebirdDialect.java | 62 ++- .../org/hibernate/dialect/HSQLDialect.java | 48 ++- .../hibernate/dialect/InformixDialect.java | 4 + .../hibernate/dialect/MimerSQLDialect.java | 31 -- .../org/hibernate/dialect/MySQLDialect.java | 72 ++-- .../org/hibernate/dialect/OracleDialect.java | 125 ++++-- .../dialect/OracleSqlAstTranslator.java | 30 +- .../hibernate/dialect/PostgreSQLDialect.java | 2 +- .../hibernate/dialect/RDMSOS2200Dialect.java | 2 +- .../hibernate/dialect/SQLServerDialect.java | 38 +- .../dialect/SQLServerSqlAstTranslator.java | 35 +- .../hibernate/dialect/SybaseASEDialect.java | 11 +- .../dialect/function/CastFunction.java | 4 +- .../function/CommonFunctionFactory.java | 2 +- .../dialect/function/DB2FormatEmulation.java | 2 +- .../IndividualLeastGreatestEmulation.java | 71 +++ .../InsertSubstringOverlayEmulation.java | 43 +- .../QuantifiedLeastGreatestEmulation.java | 74 ++++ .../function/SQLServerFormatFunction.java | 69 +++ .../java/org/hibernate/mapping/Column.java | 22 +- .../metamodel/mapping/JdbcMapping.java | 4 + .../mapping/internal/AbstractDomainPath.java | 82 ++-- .../mapping/ordering/ast/ColumnReference.java | 43 +- .../java/org/hibernate/query/CastType.java | 94 +--- .../org/hibernate/query/CastTypeKind.java | 21 - .../hql/internal/SemanticQueryBuilder.java | 71 +-- .../internal/ConcreteSqmSelectQueryPlan.java | 8 +- .../sqm/internal/SimpleDeleteQueryPlan.java | 2 +- .../sqm/internal/SimpleInsertQueryPlan.java | 2 +- .../sqm/internal/SimpleUpdateQueryPlan.java | 2 +- .../hibernate/query/sqm/internal/SqmUtil.java | 88 ++-- .../MultiTableSqmMutationConverter.java | 4 +- .../mutation/internal/cte/CteStrategy.java | 7 - .../internal/idtable/IdTableHelper.java | 2 +- .../RestrictedDeleteExecutionDelegate.java | 11 +- .../idtable/TableBasedUpdateHandler.java | 12 +- .../idtable/UpdateExecutionDelegate.java | 2 +- .../JdbcParameterBySqmParameterAccess.java | 2 +- .../sqm/sql/BaseSqmToSqlAstConverter.java | 92 +++- .../query/sqm/sql/SqmTranslation.java | 2 +- .../query/sqm/sql/StandardSqmTranslation.java | 6 +- .../main/java/org/hibernate/sql/Template.java | 3 + .../hibernate/sql/ast/SqlAstTranslator.java | 3 +- .../sql/ast/spi/AbstractSqlAstWalker.java | 232 +++++----- .../sql/ast/spi/SqlSelectAstWalker.java | 16 - .../JdbcSelectExecutorStandardImpl.java | 2 +- .../internal/DeferredResultSetAccess.java | 2 +- .../hibernate/type/NumericBooleanType.java | 5 + .../hibernate/type/OffsetDateTimeType.java | 6 + .../org/hibernate/type/TrueFalseType.java | 20 +- .../java/org/hibernate/type/YesNoType.java | 20 +- .../org/hibernate/type/ZonedDateTimeType.java | 6 + .../descriptor/sql/SqlTypeDescriptor.java | 42 ++ .../sql/VarbinaryTypeDescriptor.java | 2 +- .../type/internal/StandardBasicTypeImpl.java | 20 + .../hibernate/type/spi/TypeConfiguration.java | 3 + ...beddableElementCollectionMemberOfTest.java | 2 + .../ColumnTransformerTest.java | 7 +- .../orm/test/id/SequenceValueExtractor.java | 12 +- ...hStrategyFixWithSequenceGeneratorTest.java | 8 +- .../SequenceMismatchStrategyLogTest.java | 8 +- ...hStrategyWithoutSequenceGeneratorTest.java | 8 +- .../mapping/naturalid/inheritance/User.java | 2 + .../SortNaturalByDefaultTests.java | 2 + .../onetomany/OneToManySelfReferenceTest.java | 6 +- .../orm/test/query/hql/FunctionTests.java | 138 +++--- .../test/query/hql/StandardFunctionTests.java | 407 +++++++++--------- .../query/sql/NativeQueryParameterTests.java | 8 +- .../sql/NativeQueryResultBuilderTests.java | 6 +- .../orm/test/set/SetOperationTest.java | 34 ++ .../orm/test/sql/ast/SmokeTests.java | 12 +- ...ityWithLazyManyToOneSelfReferenceTest.java | 1 + .../EntityWithManyToOneSelfReferenceTest.java | 1 + .../testing/junit5/DialectContext.java | 59 +++ .../junit5/DialectFilterExtension.java | 30 +- .../junit5/EntityManagerFactoryAccess.java | 3 +- .../testing/junit5/SessionFactoryAccess.java | 2 +- .../orm/domain/gambit/EntityOfBasics.java | 23 +- .../testing/orm/domain/gambit/Shirt.java | 2 + .../orm/junit/DialectFeatureChecks.java | 31 +- .../orm/junit/DialectFilterExtension.java | 22 +- 97 files changed, 1917 insertions(+), 1016 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/BooleanDecoder.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/IndividualLeastGreatestEmulation.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/QuantifiedLeastGreatestEmulation.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/function/SQLServerFormatFunction.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/query/CastTypeKind.java delete mode 100644 hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlSelectAstWalker.java create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectContext.java diff --git a/docker_db.sh b/docker_db.sh index 1e832790df..fd6f182dd1 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -2,12 +2,12 @@ mysql_5_7() { docker rm -f mysql || true - docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -p3306:3306 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mysql:5.7 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci } mysql_8_0() { docker rm -f mysql || true - docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -p3306:3306 -d mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + docker run --name mysql -e MYSQL_USER=hibernate_orm_test -e MYSQL_PASSWORD=hibernate_orm_test -e MYSQL_DATABASE=hibernate_orm_test -e MYSQL_ROOT_PASSWORD=hibernate_orm_test -p3306:3306 -d mysql:8.0.21 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci } mariadb() { 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 5d31292959..b7a8be55cd 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 @@ -75,13 +75,13 @@ values // QUERY SPEC - general structure of root sqm or sub sqm queryExpression - : queryGroup queryOrder? + : simpleQueryExpression # SimpleQueryGroup + | queryExpression (setOperator simpleQueryExpression)+ # SetQueryGroup ; -queryGroup - : querySpec # QuerySpecQueryGroup - | LEFT_PAREN queryGroup RIGHT_PAREN # NestedQueryGroup - | queryGroup queryOrder? (setOperator (querySpec | LEFT_PAREN queryGroup RIGHT_PAREN))+ # SetQueryGroup +simpleQueryExpression + : querySpec queryOrder? # QuerySpecExpression + | LEFT_PAREN queryExpression RIGHT_PAREN queryOrder? # NestedQueryExpression ; setOperator diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 081f015374..a1b7871e71 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -225,6 +225,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private final boolean procedureParameterNullPassingEnabled; private final boolean collectionJoinSubqueryRewriteEnabled; private final boolean omitJoinOfSuperclassTablesEnabled; + private final int preferredSqlTypeCodeForBoolean; // Caching private boolean secondLevelCacheEnabled; @@ -415,6 +416,7 @@ public SessionFactoryOptionsBuilder(StandardServiceRegistry serviceRegistry, Boo this.procedureParameterNullPassingEnabled = cfgService.getSetting( PROCEDURE_NULL_PARAM_PASSING, BOOLEAN, false ); this.collectionJoinSubqueryRewriteEnabled = cfgService.getSetting( COLLECTION_JOIN_SUBQUERY, BOOLEAN, true ); this.omitJoinOfSuperclassTablesEnabled = cfgService.getSetting( OMIT_JOIN_OF_SUPERCLASS_TABLES, BOOLEAN, true ); + this.preferredSqlTypeCodeForBoolean = ConfigurationHelper.getPreferredSqlTypeCodeForBoolean( serviceRegistry ); final RegionFactory regionFactory = serviceRegistry.getService( RegionFactory.class ); if ( !NoCachingRegionFactory.class.isInstance( regionFactory ) ) { @@ -1204,8 +1206,11 @@ public boolean isOmitJoinOfSuperclassTablesEnabled() { return omitJoinOfSuperclassTablesEnabled; } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + @Override + public int getPreferredSqlTypeCodeForBoolean() { + return preferredSqlTypeCodeForBoolean; + } +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access public void applyBeanManager(Object beanManager) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java index af7c350d3a..716792947e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/InferredBasicValueResolver.java @@ -156,7 +156,7 @@ else if ( reflectedJtd != null ) { } @SuppressWarnings("rawtypes") - private static BasicType resolveSqlTypeIndicators( + public static BasicType resolveSqlTypeIndicators( SqlTypeDescriptorIndicators stdIndicators, BasicType resolved) { if ( resolved instanceof SqlTypeDescriptorIndicatorCapable ) { diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/RelationalObjectBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/RelationalObjectBinder.java index 477d349172..4561185881 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/RelationalObjectBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/source/internal/hbm/RelationalObjectBinder.java @@ -143,14 +143,12 @@ public void bindColumn( if ( columnSource.getSizeSource().getLength() != null ) { column.setLength( columnSource.getSizeSource().getLength() ); } + final Integer precision = columnSource.getSizeSource().getPrecision(); - if ( columnSource.getSizeSource().getScale() != null ) { + if ( precision != null && precision > 0 ) { + column.setPrecision( precision ); column.setScale( columnSource.getSizeSource().getScale() ); } - - if ( columnSource.getSizeSource().getPrecision() != null ) { - column.setPrecision( columnSource.getSizeSource().getPrecision() ); - } } column.setNullable( interpretNullability( columnSource.isNullable(), areColumnsNullableByDefault ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 43b8cd367d..f42f8ca9c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -484,4 +484,9 @@ public SqmFunctionRegistry getCustomSqmFunctionRegistry() { public boolean isOmitJoinOfSuperclassTablesEnabled() { return delegate.isOmitJoinOfSuperclassTablesEnabled(); } + + @Override + public int getPreferredSqlTypeCodeForBoolean() { + return delegate.getPreferredSqlTypeCodeForBoolean(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 02c67bb4b4..ed76dd98f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -358,4 +358,6 @@ default boolean isCollectionsInDefaultFetchGroupEnabled() { } boolean isOmitJoinOfSuperclassTablesEnabled(); + + int getPreferredSqlTypeCodeForBoolean(); } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java index de1111674f..1d47540933 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java @@ -233,7 +233,7 @@ protected void initMappingColumn( this.mappingColumn = new Column(); redefineColumnName( columnName, propertyName, applyNamingStrategy ); this.mappingColumn.setLength( length ); - if ( precision!=null ) { //relevant precision + if ( precision != null && precision > 0 ) { //relevant precision this.mappingColumn.setPrecision( precision ); this.mappingColumn.setScale( scale ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java index 1f6b07971b..2a8c8f58a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -1539,7 +1539,7 @@ public boolean supportsNoColumnsInsert() { @Override public String translateDatetimeFormat(String format) { //I don't think HANA needs FM - return OracleDialect.datetimeFormat( format, false ).result(); + return OracleDialect.datetimeFormat( format, false, false ).result(); } public boolean isUseUnicodeStringTypes() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java index ab40d93b1b..33db901749 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractTransactSQLDialect.java @@ -12,6 +12,7 @@ import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.function.IndividualLeastGreatestEmulation; import org.hibernate.dialect.identity.AbstractTransactSQLIdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -100,6 +101,9 @@ public void initializeFunctionRegistry(QueryEngine queryEngine) { CommonFunctionFactory.substring_substringLen( queryEngine ); CommonFunctionFactory.datepartDatename( queryEngine ); CommonFunctionFactory.lastDay_eomonth( queryEngine ); + + queryEngine.getSqmFunctionRegistry().register( "least", new IndividualLeastGreatestEmulation( true ) ); + queryEngine.getSqmFunctionRegistry().register( "greatest", new IndividualLeastGreatestEmulation( false ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/BooleanDecoder.java b/hibernate-core/src/main/java/org/hibernate/dialect/BooleanDecoder.java new file mode 100644 index 0000000000..fb6ccb96b2 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/BooleanDecoder.java @@ -0,0 +1,104 @@ +/* + * 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 . + */ +package org.hibernate.dialect; + +import org.hibernate.query.CastType; + +/** + * Utility for decoding boolean representations. + * + * @author Christian Beikov + */ +public final class BooleanDecoder { + + public static String toInteger(CastType from) { + switch ( from ) { + case BOOLEAN: + return "decode(?1,false,0,true,1,null)"; + case YN_BOOLEAN: + return "decode(?1,'Y',1,'N',0,null)"; + case TF_BOOLEAN: + return "decode(?1,'T',1,'F',0,null)"; + } + return null; + } + + public static String toBoolean(CastType from) { + switch ( from ) { + case STRING: + return "decode(?1,'T',true,'F',false,'Y',true,'N',false,null)"; + case YN_BOOLEAN: + return "decode(?1,'Y',true,'N',false,null)"; + case TF_BOOLEAN: + return "decode(?1,'T',true,'F',false,null)"; + case INTEGER: + case LONG: + case INTEGER_BOOLEAN: + return "decode(?1,abs(sign(?1)),1,true,0,false,null)"; + } + return null; + } + + public static String toIntegerBoolean(CastType from) { + switch ( from ) { + case STRING: + return "decode(?1,'T',1,'F',0,'Y',1,'N',0,null)"; + case YN_BOOLEAN: + return "decode(?1,'Y',1,'N',0,null)"; + case TF_BOOLEAN: + return "decode(?1,'T',1,'F',0,null)"; + case INTEGER: + case LONG: + return "abs(sign(?1))"; + } + return null; + } + + public static String toYesNoBoolean(CastType from) { + switch ( from ) { + case STRING: + return "decode(?1,'T','Y','F','N','Y','Y','N','N',null)"; + case INTEGER_BOOLEAN: + return "decode(?1,1,'Y',0,'N',null)"; + case TF_BOOLEAN: + return "decode(?1,'T','Y','F','N',null)"; + case INTEGER: + case LONG: + return "decode(abs(sign(?1)),1,'Y',0,'N',null)"; + } + return null; + } + + public static String toTrueFalseBoolean(CastType from) { + switch ( from ) { + case STRING: + return "decode(?1,'T','T','F','F','Y','T','N','F',null)"; + case INTEGER_BOOLEAN: + return "decode(?1,1,'T',0,'F',null)"; + case YN_BOOLEAN: + return "decode(?1,'Y','T','N','F',null)"; + case INTEGER: + case LONG: + return "decode(abs(sign(?1)),1,'T',0,'F',null)"; + } + return null; + } + + public static String toString(CastType from) { + switch ( from ) { + case INTEGER_BOOLEAN: + return "decode(?1,0,'false',1,'true',null)"; + case TF_BOOLEAN: + return "decode(?1,'T','true','F','false',null)"; + case YN_BOOLEAN: + return "decode(?1,'Y','true','N','false',null)"; + case BOOLEAN: + return "decode(?1,true,'true',false,'false',null)"; + } + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDDialect.java index 1ef0aefafc..e59bde88a2 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CUBRIDDialect.java @@ -304,7 +304,7 @@ public boolean supportsPartitionBy() { public String translateDatetimeFormat(String format) { //I do not know if CUBRID supports FM, but it //seems that it does pad by default, so it needs it! - return OracleDialect.datetimeFormat( format, true ) + return OracleDialect.datetimeFormat( format, true, false ) .replace("SSSSSS", "FF") .replace("SSSSS", "FF") .replace("SSSS", "FF") diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CacheDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CacheDialect.java index 498df4bc7e..92e37b82f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CacheDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CacheDialect.java @@ -394,7 +394,7 @@ public boolean supportsResultSetPositionQueryMethodsOnForwardOnlyCursor() { @Override public String translateDatetimeFormat(String format) { //I don't think Cache needs FM - return OracleDialect.datetimeFormat( format, false ).result(); + return OracleDialect.datetimeFormat( format, false, false ).result(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java index ee7dabac21..247788c6db 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -28,7 +28,6 @@ import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; -import org.hibernate.query.CastType; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.mutation.internal.cte.CteStrategy; @@ -661,11 +660,26 @@ public IdentityColumnSupport getIdentityColumnSupport() { return new DB2IdentityColumnSupport(); } + @Override + public boolean supportsValuesList() { + return true; + } + + @Override + public boolean supportsRowValueConstructorSyntaxInInList() { + return false; + } + @Override public boolean supportsPartitionBy() { return true; } + @Override + public boolean supportsNonQueryWithCTE() { + return true; + } + @Override public GroupBySummarizationRenderingStrategy getGroupBySummarizationRenderingStrategy() { return GroupBySummarizationRenderingStrategy.FUNCTION; @@ -679,7 +693,7 @@ public GroupByConstantRenderingStrategy getGroupByConstantRenderingStrategy() { @Override public String translateDatetimeFormat(String format) { //DB2 does not need nor support FM - return OracleDialect.datetimeFormat( format, false ).result(); + return OracleDialect.datetimeFormat( format, false, false ).result(); } @Override @@ -703,16 +717,6 @@ public String toBooleanValueString(boolean bool) { } } - @Override - public String castPattern(CastType from, CastType to) { - if ( getVersion() < 1100 && from == CastType.BOOLEAN && to == CastType.STRING ) { - return "case when ?1 = 1 then 'true' else 'false' end"; - } - else { - return super.castPattern( from, to ); - } - } - @Override public String extractPattern(TemporalUnit unit) { if ( unit == TemporalUnit.WEEK ) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java index cc434ee5d4..17ea62643d 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -12,6 +12,8 @@ import org.hibernate.cfg.Environment; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.DerbyConcatEmulation; +import org.hibernate.dialect.function.IndividualLeastGreatestEmulation; +import org.hibernate.dialect.function.InsertSubstringOverlayEmulation; import org.hibernate.dialect.identity.DB2IdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.pagination.AbstractLimitHandler; @@ -30,7 +32,6 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.query.CastType; -import org.hibernate.query.CastTypeKind; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.mutation.internal.idtable.AfterUseAction; @@ -60,8 +61,6 @@ import javax.persistence.TemporalType; -import static org.hibernate.query.CastType.BOOLEAN; - /** * Hibernate Dialect for Apache Derby / Cloudscape 10 * @@ -149,6 +148,13 @@ public int getDefaultDecimalPrecision() { return 31; } + @Override + public int getPreferredSqlTypeCodeForBoolean() { + return getVersion() < 1070 + ? Types.SMALLINT + : Types.BOOLEAN; + } + @Override public int getVersion() { return version; @@ -196,6 +202,7 @@ public void initializeFunctionRegistry(QueryEngine queryEngine) { queryEngine.getSqmFunctionRegistry().register( "concat", new DerbyConcatEmulation() ); //no way I can see to pad with anything other than spaces + // TODO: To support parameters, we have to inline the values queryEngine.getSqmFunctionRegistry().patternDescriptorBuilder( "lpad", "case when length(?1)0)"; - } - break; - case INTEGER: - case LONG: - if ( from == BOOLEAN && getVersion() >= 1070 ) { - return "case ?1 when false then 0 when true then 1 end"; - } - break; case STRING: + // Derby madness http://db.apache.org/derby/docs/10.8/ref/rrefsqlj33562.html + // With a nice rant: https://blog.jooq.org/2011/10/29/derby-casting-madness-the-sequel/ // See https://issues.apache.org/jira/browse/DERBY-2072 - if ( from.getKind() == CastTypeKind.NUMERIC ) { - // Use the maximum char capacity here as an intermediate type because Derby doesn't support direct conversion to varchar - return "cast(cast(?1 as char(254)) as ?2)"; + + // Since numerics can't be cast to varchar directly, use char(254) i.e. with the maximum char capacity + // as an intermediate type before converting to varchar + switch ( from ) { + case FLOAT: + case DOUBLE: + // Derby can't cast to char directly, but needs to be cast to decimal first... + return "cast(trim(cast(cast(?1 as decimal) as char(254))) as ?2)"; + case INTEGER: + case FIXED: + return "cast(trim(cast(?1 as char(254))) as ?2)"; } break; } - return super.castPattern(from, to); + return super.castPattern( from, to ); } @Override @@ -357,8 +369,8 @@ public SequenceSupport getSequenceSupport() { @Override public String getQuerySequencesString() { return getVersion() < 1060 - ? "select sys.sysschemas.schemaname as sequence_schema, sys.syssequences.* from sys.syssequences left join sys.sysschemas on sys.syssequences.schemaid = sys.sysschemas.schemaid" - : null; + ? null + : "select sys.sysschemas.schemaname as sequence_schema, sys.syssequences.* from sys.syssequences left join sys.sysschemas on sys.syssequences.schemaid = sys.sysschemas.schemaid"; } @Override 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 6690ce0e4d..4f9f69e59b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -74,7 +74,6 @@ import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.ANSICaseExpressionWalker; import org.hibernate.sql.ast.spi.CaseExpressionWalker; -import org.hibernate.sql.ast.spi.SqlAppender; import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl; @@ -152,7 +151,7 @@ public abstract class Dialect implements ConversionContext { private final UniqueDelegate uniqueDelegate; - private DefaultSizeStrategy defaultSizeStrategy; + private final SizeStrategy sizeStrategy; // constructors and factory methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -242,7 +241,7 @@ protected Dialect() { } uniqueDelegate = new DefaultUniqueDelegate( this ); - defaultSizeStrategy = new DefaultSizeStrategyImpl(); + sizeStrategy = new SizeStrategyImpl(); } /** @@ -442,7 +441,7 @@ public void initializeFunctionRegistry(QueryEngine queryEngine) { //very few databases support ANSI-style overlay() function, so emulate //it here in terms of either insert() or concat()/substring() - queryEngine.getSqmFunctionRegistry().register("overlay", new InsertSubstringOverlayEmulation()); + queryEngine.getSqmFunctionRegistry().register("overlay", new InsertSubstringOverlayEmulation( false )); //ANSI SQL trim() function is supported on almost all of the databases //we care about, but on some it must be emulated using ltrim(), rtrim(), @@ -634,6 +633,88 @@ public String extractPattern(TemporalUnit unit) { * type the value argument is cast to */ public String castPattern(CastType from, CastType to) { + switch ( to ) { + case STRING: + switch ( from ) { + case INTEGER_BOOLEAN: + return "case ?1 when 1 then 'true' when 0 then 'false' else null end"; + case YN_BOOLEAN: + return "case ?1 when 'Y' then 'true' when 'N' then 'false' else null end"; + case TF_BOOLEAN: + return "case ?1 when 'T' then 'true' when 'F' then 'false' else null end"; + } + break; + case INTEGER: + case LONG: + switch ( from ) { + case YN_BOOLEAN: + return "case ?1 when 'Y' then 1 when 'N' then 0 else null end"; + case TF_BOOLEAN: + return "case ?1 when 'T' then 1 when 'F' then 0 else null end"; + case BOOLEAN: + return "case ?1 when true then 1 when false then 0 else null end"; + } + break; + case INTEGER_BOOLEAN: + switch ( from ) { + case STRING: + return "case ?1 when 'T' then 1 when 'Y' then 1 when 'F' then 0 when 'N' then 0 else null end"; + case INTEGER: + case LONG: + return "abs(sign(?1))"; + case YN_BOOLEAN: + return "case ?1 when 'Y' then 1 when 'N' then 0 else null end"; + case TF_BOOLEAN: + return "case ?1 when 'T' then 1 when 'F' then 0 else null end"; + case BOOLEAN: + return "case ?1 when true then 1 when false then 0 else null end"; + } + break; + case YN_BOOLEAN: + switch ( from ) { + case STRING: + return "case ?1 when 'T' then 'Y' when 'Y' then 'Y' when 'F' then 'N' when 'N' then 'N' else null end"; + case INTEGER_BOOLEAN: + return "case ?1 when 1 then 'Y' when 0 then 'N' else null end"; + case INTEGER: + case LONG: + return "case abs(sign(?1)) when 1 then 'Y' when 0 then 'N' else null end"; + case TF_BOOLEAN: + return "case ?1 when 'T' then 'Y' when 'F' then 'N' else null end"; + case BOOLEAN: + return "case ?1 when true then 'Y' when false then 'N' else null end"; + } + break; + case TF_BOOLEAN: + switch ( from ) { + case STRING: + return "case ?1 when 'T' then 'T' when 'Y' then 'T' when 'F' then 'F' when 'N' then 'F' else null end"; + case INTEGER_BOOLEAN: + return "case ?1 when 1 then 'T' when 0 then 'F' else null end"; + case INTEGER: + case LONG: + return "case abs(sign(?1)) when 1 then 'T' when 0 then 'F' else null end"; + case YN_BOOLEAN: + return "case ?1 when 'Y' then 'T' when 'N' then 'F' else null end"; + case BOOLEAN: + return "case ?1 when true then 'T' when false then 'F' else null end"; + } + break; + case BOOLEAN: + switch ( from ) { + case STRING: + return "case ?1 when 'T' then true when 'Y' then true when 'F' then false when 'N' then false else null end"; + case INTEGER_BOOLEAN: + case INTEGER: + case LONG: + return "(?1<>0)"; + case YN_BOOLEAN: + return "(?1<>'N')"; + case TF_BOOLEAN: + return "(?1<>'F')"; + } + break; + } return "cast(?1 as ?2)"; } @@ -913,9 +994,12 @@ public String getCastTypeName(SqlExpressable type, Long length, Integer precisio Size size; if ( length == null && precision == null ) { //use defaults - size = getDefaultSizeStrategy().resolveDefaultSize( + size = getSizeStrategy().resolveSize( type.getJdbcMapping().getSqlTypeDescriptor(), - type.getJdbcMapping().getJavaTypeDescriptor() + type.getJdbcMapping().getJavaTypeDescriptor(), + precision, + scale, + length ); } else { @@ -3399,12 +3483,8 @@ public boolean supportsSelectAliasInGroupByClause() { return false; } - public DefaultSizeStrategy getDefaultSizeStrategy(){ - return defaultSizeStrategy; - } - - public void setDefaultSizeStrategy(DefaultSizeStrategy defaultSizeStrategy){ - this.defaultSizeStrategy = defaultSizeStrategy; + public SizeStrategy getSizeStrategy() { + return sizeStrategy; } public long getDefaultLobLength() { @@ -3519,38 +3599,63 @@ public String formatBinaryLiteral(byte[] bytes) { /** * Pluggable strategy for determining the Size to use for columns of - * a given SQL type when no explicit Size has been given. + * a given SQL type. * - * Allows Dialects, integrators and users a chance to apply default - * column size limits in certain situations based on the mapped + * Allows Dialects, integrators and users a chance to apply + * column size defaults and limits in certain situations based on the mapped * SQL and Java types. E.g. when mapping a UUID to a VARCHAR column * we know the default Size should be `Size#length == 36`. */ - public interface DefaultSizeStrategy { + public interface SizeStrategy { /** - * Resolve the default {@link Size} to use for columns of the given + * Resolve the {@link Size} to use for columns of the given * {@link SqlTypeDescriptor SQL type} and {@link JavaTypeDescriptor Java type}. * * @return a non-null {@link Size} */ - Size resolveDefaultSize(SqlTypeDescriptor sqlType, JavaTypeDescriptor javaType); + Size resolveSize( + SqlTypeDescriptor sqlType, + JavaTypeDescriptor javaType, + Integer precision, + Integer scale, Long length); } - public class DefaultSizeStrategyImpl implements DefaultSizeStrategy { + public class SizeStrategyImpl implements SizeStrategy { @Override - public Size resolveDefaultSize(SqlTypeDescriptor sqlType, JavaTypeDescriptor javaType) { + public Size resolveSize( + SqlTypeDescriptor sqlType, + JavaTypeDescriptor javaType, + Integer precision, + Integer scale, + Long length) { final Size size = new Size(); int jdbcTypeCode = sqlType.getJdbcTypeCode(); switch (jdbcTypeCode) { case Types.BIT: + // Use the default length for Boolean if we encounter the JPA default 255 instead + if ( javaType.getJavaType() == Boolean.class && length != null && length == 255 ) { + length = null; + } + size.setLength( javaType.getDefaultSqlLength( Dialect.this ) ); + break; case Types.CHAR: + // Use the default length for char if we encounter the JPA default 255 instead + if ( javaType.getJavaType() == Character.class && length != null && length == 255 ) { + length = null; + } + size.setLength( javaType.getDefaultSqlLength( Dialect.this ) ); + break; case Types.NCHAR: case Types.BINARY: case Types.VARCHAR: case Types.NVARCHAR: case Types.VARBINARY: - size.setLength( javaType.getDefaultSqlLength(Dialect.this) ); + // Use the default length for UUID if we encounter the JPA default 255 instead + if ( javaType.getJavaType() == UUID.class && length != null && length == 255 ) { + length = null; + } + size.setLength( javaType.getDefaultSqlLength( Dialect.this ) ); break; case Types.LONGVARCHAR: case Types.LONGNVARCHAR: @@ -3562,11 +3667,11 @@ public Size resolveDefaultSize(SqlTypeDescriptor sqlType, JavaTypeDescriptor jav case Types.REAL: case Types.TIMESTAMP: case Types.TIMESTAMP_WITH_TIMEZONE: - size.setPrecision( javaType.getDefaultSqlPrecision(Dialect.this) ); + size.setPrecision( javaType.getDefaultSqlPrecision( Dialect.this ) ); break; case Types.NUMERIC: case Types.DECIMAL: - size.setPrecision( javaType.getDefaultSqlPrecision(Dialect.this) ); + size.setPrecision( javaType.getDefaultSqlPrecision( Dialect.this ) ); size.setScale( javaType.getDefaultSqlScale() ); break; case Types.CLOB: @@ -3575,6 +3680,16 @@ public Size resolveDefaultSize(SqlTypeDescriptor sqlType, JavaTypeDescriptor jav break; } + + if ( precision != null ) { + size.setPrecision( precision ); + } + if ( scale != null ) { + size.setScale( scale ); + } + if ( length != null ) { + size.setLength( length ); + } return size; } } @@ -3620,7 +3735,7 @@ public String translateDatetimeFormat(String format) { //most databases support a datetime format //copied from Oracle's to_char() function, //with some minor variation - return OracleDialect.datetimeFormat( format, true ).result(); + return OracleDialect.datetimeFormat( format, true, false ).result(); } /** diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdDialect.java index 7b3498c615..9f56e2b6dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/FirebirdDialect.java @@ -61,7 +61,6 @@ import javax.persistence.TemporalType; -import static org.hibernate.query.CastType.*; import static org.hibernate.type.descriptor.DateTimeUtils.formatAsTimestampWithMillis; /** @@ -219,24 +218,49 @@ protected SqlAstTranslator buildTranslator( */ @Override public String castPattern(CastType from, CastType to) { - if ( to==BOOLEAN - && (from==LONG || from==INTEGER)) { - return "(0<>?1)"; - } - if ( getVersion() < 300 ) { - if ( to==BOOLEAN && from==STRING ) { -// return "iif(lower(?1) similar to 't|f|true|false', lower(?1) like 't%', null)"; - return "decode(lower(?1),'t',1,'f',0,'true',1,'false',0)"; - } - if ( to==STRING && from==BOOLEAN ) { - return "trim(decode(?1,0,'false','true'))"; - } - } - else { - if ( from==BOOLEAN - && (to==LONG || to==INTEGER)) { - return "decode(?1,false,0,true,1)"; - } + String result; + switch ( to ) { + case FLOAT: + return "cast(double(?1) as real)"; + case DOUBLE: + return "double(?1)"; + case INTEGER: + case LONG: + result = BooleanDecoder.toInteger( from ); + if ( result != null ) { + return result; + } + break; + case BOOLEAN: + result = BooleanDecoder.toBoolean( from ); + if ( result != null ) { + return result; + } + break; + case INTEGER_BOOLEAN: + result = BooleanDecoder.toIntegerBoolean( from ); + if ( result != null ) { + return result; + } + break; + case YN_BOOLEAN: + result = BooleanDecoder.toYesNoBoolean( from ); + if ( result != null ) { + return result; + } + break; + case TF_BOOLEAN: + result = BooleanDecoder.toTrueFalseBoolean( from ); + if ( result != null ) { + return result; + } + break; + case STRING: + result = BooleanDecoder.toString( from ); + if ( result != null ) { + return result; + } + break; } return super.castPattern( from, to ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index 374b2520ea..7993160a0d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -65,9 +65,6 @@ import org.jboss.logging.Logger; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; -import static org.hibernate.query.CastType.BOOLEAN; -import static org.hibernate.query.CastType.INTEGER; -import static org.hibernate.query.CastType.LONG; /** * An SQL dialect compatible with HyperSQL (HSQLDB) version 1.8 and above. @@ -237,12 +234,47 @@ protected SqlAstTranslator buildTranslator( } }; } - @Override public String castPattern(CastType from, CastType to) { - if ( from== BOOLEAN - && (to== INTEGER || to== LONG)) { - return "casewhen(?1,1,0)"; + String result; + switch ( to ) { + case INTEGER: + case LONG: + result = BooleanDecoder.toInteger( from ); + if ( result != null ) { + return result; + } + break; + case BOOLEAN: + result = BooleanDecoder.toBoolean( from ); + if ( result != null ) { + return result; + } + break; + case INTEGER_BOOLEAN: + result = BooleanDecoder.toIntegerBoolean( from ); + if ( result != null ) { + return result; + } + break; + case YN_BOOLEAN: + result = BooleanDecoder.toYesNoBoolean( from ); + if ( result != null ) { + return result; + } + break; + case TF_BOOLEAN: + result = BooleanDecoder.toTrueFalseBoolean( from ); + if ( result != null ) { + return result; + } + break; + case STRING: + result = BooleanDecoder.toString( from ); + if ( result != null ) { + return result; + } + break; } return super.castPattern( from, to ); } @@ -647,7 +679,7 @@ public String getCascadeConstraintsString() { @Override public String translateDatetimeFormat(String format) { - return OracleDialect.datetimeFormat(format, false) + return OracleDialect.datetimeFormat( format, false, false ) .replace("SSSSSS", "FF") .replace("SSSSS", "FF") .replace("SSSS", "FF") 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 9a81a53215..5efe95eeae 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/InformixDialect.java @@ -8,6 +8,7 @@ import org.hibernate.boot.TempTableDdlTransactionHandling; import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.function.IndividualLeastGreatestEmulation; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.InformixIdentityColumnSupport; import org.hibernate.dialect.pagination.FirstLimitHandler; @@ -163,6 +164,9 @@ public void initializeFunctionRegistry(QueryEngine queryEngine) { ).setArgumentListSignature("(pattern, string[, start])"); //coalesce() and nullif() both supported since Informix 12 + + queryEngine.getSqmFunctionRegistry().register( "least", new IndividualLeastGreatestEmulation( true ) ); + queryEngine.getSqmFunctionRegistry().register( "greatest", new IndividualLeastGreatestEmulation( false ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java index 80f623b151..59c37ff6ed 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MimerSQLDialect.java @@ -16,7 +16,6 @@ import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.query.CastType; import org.hibernate.query.SemanticException; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; @@ -32,8 +31,6 @@ import javax.persistence.TemporalType; -import static org.hibernate.query.CastType.BOOLEAN; - /** * A dialect for Mimer SQL 11. * @@ -124,34 +121,6 @@ protected SqlAstTranslator buildTranslator( }; } - /** - * Mimer does have a real {@link java.sql.Types#BOOLEAN} - * type, but it doesn't know how to cast to it. - */ - @Override - public String castPattern(CastType from, CastType to) { - switch (to) { - case BOOLEAN: - switch (from) { - case STRING: -// return "case when regexp_match(lower(?1), '^(t|f|true|false)$') then lower(?1) like 't%' end"; -// return "case when lower(?1)in('t','true') then true when lower(?1)in('f','false') then false end"; - return "case when ?1 in('t','true','T','TRUE') then true when ?1 in('f','false','F','FALSE') then false end"; - case LONG: - case INTEGER: - return "(?1<>0)"; - } - break; - case INTEGER: - case LONG: - if (from == BOOLEAN) { - return "case ?1 when false then 0 when true then 1 end"; - } - break; - } - return super.castPattern(from, to); - } - @Override public String currentTimestamp() { return "localtimestamp"; 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 87145105ab..6d00209b32 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -22,6 +22,7 @@ import org.hibernate.dialect.sequence.SequenceSupport; import org.hibernate.dialect.unique.MySQLUniqueDelegate; import org.hibernate.dialect.unique.UniqueDelegate; +import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.exception.LockAcquisitionException; @@ -33,7 +34,6 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.SqlExpressable; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; -import org.hibernate.query.CastType; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.sqm.mutation.internal.idtable.AfterUseAction; @@ -47,6 +47,8 @@ import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import java.sql.CallableStatement; import java.sql.ResultSet; @@ -56,7 +58,6 @@ import javax.persistence.TemporalType; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; -import static org.hibernate.query.CastType.BOOLEAN; /** * An SQL dialect for MySQL (prior to 5.x). @@ -68,6 +69,7 @@ public class MySQLDialect extends Dialect { private final UniqueDelegate uniqueDelegate; private final MySQLStorageEngine storageEngine; private final int version; + private final SizeStrategy sizeStrategy; public MySQLDialect(DialectResolutionInfo info) { this( info.getDatabaseMajorVersion() * 100 + info.getDatabaseMinorVersion() * 10 ); @@ -88,10 +90,10 @@ public MySQLDialect(int version) { if (storageEngine == null) { this.storageEngine = getDefaultMySQLStorageEngine(); } - else if( "innodb".equals( storageEngine.toLowerCase() ) ) { + else if( "innodb".equalsIgnoreCase( storageEngine ) ) { this.storageEngine = InnoDBStorageEngine.INSTANCE; } - else if( "myisam".equals( storageEngine.toLowerCase() ) ) { + else if( "myisam".equalsIgnoreCase( storageEngine ) ) { this.storageEngine = MyISAMStorageEngine.INSTANCE; } else { @@ -163,6 +165,25 @@ else if( "myisam".equals( storageEngine.toLowerCase() ) ) { getDefaultProperties().setProperty( Environment.STATEMENT_BATCH_SIZE, DEFAULT_BATCH_SIZE ); uniqueDelegate = new MySQLUniqueDelegate( this ); + sizeStrategy = new SizeStrategyImpl() { + @Override + public Size resolveSize( + SqlTypeDescriptor sqlType, + JavaTypeDescriptor javaType, + Integer precision, + Integer scale, + Long length) { + final int jdbcTypeCode = sqlType.getSqlType(); + switch ( jdbcTypeCode ) { + case Types.BIT: + // MySQL allows BIT with a length up to 64 + if ( length != null ) { + return Size.length( Math.min( Math.max( length, 1 ), 64 ) ); + } + } + return super.resolveSize( sqlType, javaType, precision, scale, length ); + } + }; } @Override @@ -174,6 +195,11 @@ public int getMySQLVersion() { return version; } + @Override + public SizeStrategy getSizeStrategy() { + return sizeStrategy; + } + @Override public long getDefaultLobLength() { //max length for mediumblob or mediumtext @@ -334,31 +360,6 @@ public String extractPattern(TemporalUnit unit) { } } - /** - * MySQL doesn't have a real {@link java.sql.Types#BOOLEAN} - * type, so... - */ - @Override - public String castPattern(CastType from, CastType to) { - switch (to) { - case BOOLEAN: - switch (from) { - case STRING: -// return "if(?1 rlike '^(t|f|true|false)$', ?1 like 't%', null)"; - return "if(lower(?1) in('t','f','true','false'), ?1 like 't%', null)"; - case LONG: - case INTEGER: - return "(?1<>0)"; - } - case STRING: - if (from == BOOLEAN) { - return "if(?1,'true','false')"; - } - default: - return super.castPattern(from, to); - } - } - @Override public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType) { switch (unit) { @@ -717,11 +718,14 @@ public SQLExceptionConversionDelegate buildSQLExceptionConversionDelegate() { return new LockAcquisitionException( message, sqlException, sql ); } - switch ( JdbcExceptionHelper.extractSqlState( sqlException ) ) { - case "41000": - return new LockTimeoutException(message, sqlException, sql); - case "40001": - return new LockAcquisitionException(message, sqlException, sql); + final String sqlState = JdbcExceptionHelper.extractSqlState( sqlException ); + if ( sqlState != null ) { + switch ( sqlState ) { + case "41000": + return new LockTimeoutException( message, sqlException, sql ); + case "40001": + return new LockAcquisitionException( message, sqlException, sql ); + } } return null; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index 922ba70241..4a6cca0082 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -70,7 +70,6 @@ import javax.persistence.TemporalType; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; -import static org.hibernate.query.CastType.STRING; import static org.hibernate.query.TemporalUnit.*; /** @@ -234,19 +233,40 @@ public String currentTimestampWithTimeZone() { */ @Override public String castPattern(CastType from, CastType to) { - switch (to) { - case BOOLEAN: - switch (from) { - case STRING: - return "decode(lower(?1),'t',1,'f',0,'true',1,'false',0)"; - case LONG: - case INTEGER: - return "abs(sign(?1))"; + String result; + switch ( to ) { + case INTEGER: + case LONG: + result = BooleanDecoder.toInteger( from ); + if ( result != null ) { + return result; } + break; + case INTEGER_BOOLEAN: + result = BooleanDecoder.toIntegerBoolean( from ); + if ( result != null ) { + return result; + } + break; + case YN_BOOLEAN: + result = BooleanDecoder.toYesNoBoolean( from ); + if ( result != null ) { + return result; + } + break; + case BOOLEAN: + case TF_BOOLEAN: + result = BooleanDecoder.toTrueFalseBoolean( from ); + if ( result != null ) { + return result; + } + break; case STRING: - switch (from) { - case BOOLEAN: - return "decode(?1,0,'false','true')"; + switch ( from ) { + case INTEGER_BOOLEAN: + case TF_BOOLEAN: + case YN_BOOLEAN: + return BooleanDecoder.toString( from ); case DATE: return "to_char(?1,'YYYY-MM-DD')"; case TIME: @@ -258,29 +278,34 @@ public String castPattern(CastType from, CastType to) { case ZONE_TIMESTAMP: return "to_char(?1,'YYYY-MM-DD HH24:MI:SS.FF9 TZR')"; } + break; case DATE: - if (from == STRING) { + if ( from == CastType.STRING ) { return "to_date(?1,'YYYY-MM-DD')"; } + break; case TIME: - if (from == STRING) { + if ( from == CastType.STRING ) { return "to_date(?1,'HH24:MI:SS')"; } + break; case TIMESTAMP: - if (from == STRING) { + if ( from == CastType.STRING ) { return "to_timestamp(?1,'YYYY-MM-DD HH24:MI:SS.FF9')"; } + break; case OFFSET_TIMESTAMP: - if (from == STRING) { + if ( from == CastType.STRING ) { return "to_timestamp_tz(?1,'YYYY-MM-DD HH24:MI:SS.FF9TZH:TZM')"; } + break; case ZONE_TIMESTAMP: - if (from == STRING) { + if ( from == CastType.STRING ) { return "to_timestamp_tz(?1,'YYYY-MM-DD HH24:MI:SS.FF9 TZR')"; } - default: - return super.castPattern(from, to); + break; } + return super.castPattern(from, to); } /** @@ -315,6 +340,20 @@ public String extractPattern(TemporalUnit unit) { return "to_number(to_char(?2,'DDD'))"; case WEEK: return "to_number(to_char(?2,'IW'))"; //the ISO week number + case WEEK_OF_YEAR: + return "to_number(to_char(?2,'WW'))"; + // Oracle doesn't support extracting the quarter + case QUARTER: + return "to_number(to_char(?2,'Q'))"; + // Oracle can't extract time parts from a date column, so we need to cast to timestamp + // This is because Oracle treats date as ANSI SQL date which has no time part + // Also see https://docs.oracle.com/cd/B28359_01/server.111/b28286/functions052.htm#SQLRF00639 + case HOUR: + return "to_number(to_char(?2,'HH24'))"; + case MINUTE: + return "to_number(to_char(?2,'MI'))"; + case SECOND: + return "to_number(to_char(?2,'SS'))"; default: return super.extractPattern(unit); } @@ -855,7 +894,11 @@ public SqmMultiTableMutationStrategy getFallbackSqmMutationStrategy( EntityMappingType rootEntityDescriptor, RuntimeModelCreationContext runtimeModelCreationContext) { return new GlobalTemporaryTableStrategy( - new IdTable( rootEntityDescriptor, name -> name.length() > 30 ? name.substring( 0, 30 ) : name, this ), + new IdTable( + rootEntityDescriptor, + name -> "HT_" + ( name.length() > 27 ? name.substring( 0, 27 ) : name ), + this + ), () -> new TempIdTableExporter( false, this::getTypeName ) { @Override protected String getCreateOptions() { @@ -1050,8 +1093,16 @@ public String getReadLockString(String aliases, int timeout) { return getWriteLockString( aliases, timeout ); } - public static Replacer datetimeFormat(String format, boolean useFm) { + @Override + public String translateDatetimeFormat(String format) { + // Unlike other databases, Oracle requires an explicit reset for the fm modifier, + // otherwise all following pattern variables trim zeros + return datetimeFormat( format, true, true ).result(); + } + + public static Replacer datetimeFormat(String format, boolean useFm, boolean resetFm) { String fm = useFm ? "fm" : ""; + String fmReset = resetFm ? fm : ""; return new Replacer( format, "'", "\"" ) //era .replace("GG", "AD") @@ -1059,42 +1110,42 @@ public static Replacer datetimeFormat(String format, boolean useFm) { //year .replace("yyyy", "YYYY") - .replace("yyy", fm + "YYYY") + .replace("yyy", fm + "YYYY" + fmReset) .replace("yy", "YY") - .replace("y", fm + "YYYY") + .replace("y", fm + "YYYY" + fmReset) //month of year - .replace("MMMM", fm + "Month") + .replace("MMMM", fm + "Month" + fmReset) .replace("MMM", "Mon") .replace("MM", "MM") - .replace("M", fm + "MM") + .replace("M", fm + "MM" + fmReset) //week of year .replace("ww", "IW") - .replace("w", fm + "IW") + .replace("w", fm + "IW" + fmReset) //year for week .replace("YYYY", "IYYY") - .replace("YYY", fm + "IYYY") + .replace("YYY", fm + "IYYY" + fmReset) .replace("YY", "IY") - .replace("Y", fm + "IYYY") + .replace("Y", fm + "IYYY" + fmReset) //week of month .replace("W", "W") //day of week - .replace("EEEE", fm + "Day") + .replace("EEEE", fm + "Day" + fmReset) .replace("EEE", "Dy") .replace("ee", "D") - .replace("e", fm + "D") + .replace("e", fm + "D" + fmReset) //day of month .replace("dd", "DD") - .replace("d", fm + "DD") + .replace("d", fm + "DD" + fmReset) //day of year .replace("DDD", "DDD") - .replace("DD", fm + "DDD") - .replace("D", fm + "DDD") + .replace("DD", fm + "DDD" + fmReset) + .replace("D", fm + "DDD" + fmReset) //am pm .replace("aa", "AM") @@ -1103,16 +1154,16 @@ public static Replacer datetimeFormat(String format, boolean useFm) { //hour .replace("hh", "HH12") .replace("HH", "HH24") - .replace("h", fm + "HH12") - .replace("H", fm + "HH24") + .replace("h", fm + "HH12" + fmReset) + .replace("H", fm + "HH24" + fmReset) //minute .replace("mm", "MI") - .replace("m", fm + "MI") + .replace("m", fm + "MI" + fmReset) //second .replace("ss", "SS") - .replace("s", fm + "SS") + .replace("s", fm + "SS" + fmReset) //fractional seconds .replace("SSSSSS", "FF6") diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java index 3a1c3cb770..4a77505185 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleSqlAstTranslator.java @@ -6,10 +6,14 @@ */ package org.hibernate.dialect; -import org.hibernate.FetchClauseType; +import java.util.List; + import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.collections.Stack; +import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.insert.Values; import org.hibernate.sql.ast.tree.select.QueryGroup; import org.hibernate.sql.ast.tree.select.QueryPart; import org.hibernate.sql.ast.tree.select.QuerySpec; @@ -34,6 +38,30 @@ protected boolean shouldEmulateFetchClause(QueryPart queryPart) { ); } + @Override + protected void visitValuesList(List valuesList) { + if ( valuesList.size() < 2 ) { + super.visitValuesList( valuesList ); + } + else { + // Oracle doesn't support a multi-values insert + // So we render a select union emulation instead + String separator = ""; + final Stack clauseStack = getClauseStack(); + try { + clauseStack.push( Clause.VALUES ); + for ( Values values : valuesList ) { + appendSql( separator ); + renderExpressionsAsSubquery( values.getExpressions() ); + separator = " union all "; + } + } + finally { + clauseStack.pop(); + } + } + } + @Override public void visitQueryGroup(QueryGroup queryGroup) { if ( shouldEmulateFetchClause( queryGroup ) ) { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java index 40cc040e32..b39667c7bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -669,7 +669,7 @@ public String translateDatetimeFormat(String format) { } public Replacer datetimeFormat(String format) { - return OracleDialect.datetimeFormat(format, true) + return OracleDialect.datetimeFormat( format, true, false ) .replace("SSSSSS", "US") .replace("SSSSS", "US") .replace("SSSS", "US") diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java index 7d78dfeba0..c5fd631d7e 100755 --- a/hibernate-core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/RDMSOS2200Dialect.java @@ -343,7 +343,7 @@ public LockingStrategy getLockingStrategy(Lockable lockable, LockMode lockMode) @Override public String translateDatetimeFormat(String format) { - return OracleDialect.datetimeFormat( format, true ) //Does it really support FM? + return OracleDialect.datetimeFormat( format, true, false ) //Does it really support FM? .replace("SSSSSS", "MLS") .replace("SSSSS", "MLS") .replace("SSSS", "MLS") 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 446013f44c..18eb81c75a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -8,6 +8,7 @@ import org.hibernate.*; import org.hibernate.dialect.function.CommonFunctionFactory; +import org.hibernate.dialect.function.SQLServerFormatFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.SQLServerIdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; @@ -22,6 +23,7 @@ import org.hibernate.exception.LockTimeoutException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.internal.util.JdbcExceptionHelper; +import org.hibernate.query.CastType; import org.hibernate.query.TemporalUnit; import org.hibernate.query.spi.QueryEngine; import org.hibernate.sql.ast.SqlAstTranslator; @@ -146,7 +148,7 @@ public void initializeFunctionRegistry(QueryEngine queryEngine) { } if ( getVersion() >= 11 ) { - CommonFunctionFactory.format_format( queryEngine ); + queryEngine.getSqmFunctionRegistry().register( "format", new SQLServerFormatFunction( this ) ); //actually translate() was added in 2017 but //it's not worth adding a new dialect for that! @@ -192,6 +194,22 @@ protected SqlAstTranslator buildTranslator( }; } + @Override + public String castPattern(CastType from, CastType to) { + if ( to == CastType.STRING ) { + switch ( from ) { + case TIMESTAMP: + // SQL Server uses yyyy-MM-dd HH:mm:ss.nnnnnnn by default when doing a cast, but only need second precision + return "format(?1,'yyyy-MM-dd HH:mm:ss')"; + case TIME: + // SQL Server uses HH:mm:ss.nnnnnnn by default when doing a cast, but only need second precision + // SQL Server requires quoting of ':' in time formats and the use of 'hh' instead of 'HH' + return "format(?1,'hh\\:mm\\:ss')"; + } + } + return super.castPattern( from, to ); + } + @Override public String currentTimestamp() { return "sysdatetime()"; @@ -199,7 +217,7 @@ public String currentTimestamp() { @Override public String currentTime() { - return currentTimestamp(); + return "convert(time, getdate())"; } @Override @@ -502,16 +520,25 @@ public long getFractionalSecondPrecisionInNanos() { @Override public String extractPattern(TemporalUnit unit) { switch (unit) { + case TIMEZONE_HOUR: + return "(datepart(tz,?2)/60)"; + case TIMEZONE_MINUTE: + return "(datepart(tz,?2)%60)"; //currently Dialect.extract() doesn't need //to handle NANOSECOND (might change that?) // case NANOSECOND: // //this should evaluate to a bigint type -// return "(datepart(second,?2,?3)*1000000000+datepart(nanosecond,?2,?3))"; +// return "(datepart(second,?2)*1000000000+datepart(nanosecond,?2))"; case SECOND: //this should evaluate to a floating point type - return "(datepart(second,?2,?3)+datepart(nanosecond,?2,?3)/1e9)"; + return "(datepart(second,?2)+datepart(nanosecond,?2)/1e9)"; + case WEEK: + // Thanks https://www.sqlservercentral.com/articles/a-simple-formula-to-calculate-the-iso-week-number + if ( getVersion() < 10 ) { + return "(DATEPART(dy,DATEADD(dd,DATEDIFF(dd,'17530101',?2)/7*7,'17530104'))+6)/7)"; + } default: - return "datepart(?1,?2,?3)"; + return "datepart(?1,?2)"; } } @@ -565,6 +592,7 @@ public String translateExtractField(TemporalUnit unit) { switch ( unit ) { //the ISO week number (behavior of "week" depends on a system property) case WEEK: return "isowk"; + case OFFSET: return "tz"; default: return super.translateExtractField(unit); } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerSqlAstTranslator.java index 29cb06e799..97c021e7de 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerSqlAstTranslator.java @@ -45,17 +45,40 @@ protected OffsetFetchClauseMode getOffsetFetchClauseMode(QueryPart queryPart) { hasLimit = queryPart.getFetchClauseExpression() != null; hasOffset = queryPart.getOffsetClauseExpression() != null; } - if ( version < 9 || !hasOffset ) { - return hasLimit ? OffsetFetchClauseMode.TOP_ONLY : null; - } - else if ( version < 11 || !isRowsOnlyFetchClauseType( queryPart ) ) { - return OffsetFetchClauseMode.EMULATED; + if ( queryPart instanceof QueryGroup ) { + // We can't use TOP for set operations + if ( hasOffset || hasLimit ) { + if ( version < 11 || !isRowsOnlyFetchClauseType( queryPart ) ) { + return OffsetFetchClauseMode.EMULATED; + } + else { + return OffsetFetchClauseMode.STANDARD; + } + } + + return null; } else { - return OffsetFetchClauseMode.STANDARD; + if ( version < 9 || !hasOffset ) { + return hasLimit ? OffsetFetchClauseMode.TOP_ONLY : null; + } + else if ( version < 11 || !isRowsOnlyFetchClauseType( queryPart ) ) { + return OffsetFetchClauseMode.EMULATED; + } + else { + return OffsetFetchClauseMode.STANDARD; + } } } + @Override + protected boolean supportsSimpleQueryGrouping() { + // SQL Server is quite strict i.e. it requires `select .. union all select * from (select ...)` + // rather than `select .. union all (select ...)` because parenthesis followed by select + // is always treated as a subquery, which is not supported in a set operation + return false; + } + protected boolean shouldEmulateFetchClause(QueryPart queryPart) { // Check if current query part is already row numbering to avoid infinite recursion return getQueryPartForRowNumbering() != queryPart && getOffsetFetchClauseMode( queryPart ) == OffsetFetchClauseMode.EMULATED; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java index 630b2c3408..759ecefe09 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseASEDialect.java @@ -7,6 +7,7 @@ package org.hibernate.dialect; import org.hibernate.LockOptions; +import org.hibernate.dialect.function.QuantifiedLeastGreatestEmulation; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.TopLimitHandler; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; @@ -16,6 +17,7 @@ import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.query.TrimSpec; +import org.hibernate.query.spi.QueryEngine; import org.hibernate.sql.ForUpdateFragment; import org.hibernate.sql.JoinFragment; import org.hibernate.sql.Sybase11JoinFragment; @@ -340,7 +342,14 @@ private void registerSybaseKeywords() { registerKeyword( "xmlvalidate" ); } - // Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + @Override + public void initializeFunctionRegistry(QueryEngine queryEngine) { + super.initializeFunctionRegistry( queryEngine ); + + queryEngine.getSqmFunctionRegistry().register( "least", new QuantifiedLeastGreatestEmulation( true ) ); + queryEngine.getSqmFunctionRegistry().register( "greatest", new QuantifiedLeastGreatestEmulation( false ) ); + } +// Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastFunction.java index 9da78e4e0a..4662cb611c 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/CastFunction.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/CastFunction.java @@ -42,11 +42,11 @@ public CastFunction(Dialect dialect) { public void render(SqlAppender sqlAppender, List arguments, SqlAstWalker walker) { final Expression source = (Expression) arguments.get( 0 ); final JdbcMapping sourceMapping = ( (SqlExpressable) source.getExpressionType() ).getJdbcMapping(); - final CastType sourceType = CastType.from( sourceMapping ); + final CastType sourceType = sourceMapping.getCastType(); final CastTarget castTarget = (CastTarget) arguments.get( 1 ); final JdbcMapping targetJdbcMapping = castTarget.getExpressionType().getJdbcMapping(); - final CastType targetType = CastType.from( targetJdbcMapping ); + final CastType targetType = targetJdbcMapping.getCastType(); String cast = dialect.castPattern( sourceType, targetType ); 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 4d51ebdfb3..28ed4858ba 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 @@ -1254,7 +1254,7 @@ public static void substring_substringLen(QueryEngine queryEngine) { .registerBinaryTernaryPattern( "substring", StandardBasicTypes.STRING, - "substring(?1,?2,len(?1)-?2)", + "substring(?1,?2,len(?1)-?2+1)", "substring(?1,?2,?3)" ) .setArgumentListSignature("(string{ from|,} start[{ for|,} length])"); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2FormatEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2FormatEmulation.java index 2b15a8be37..8942ed931d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2FormatEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/DB2FormatEmulation.java @@ -50,7 +50,7 @@ public void render( final Format format = (Format) arguments.get(1); sqlAppender.appendSql("("); - String[] bits = OracleDialect.datetimeFormat( format.getFormat(), false ).result().split("\""); + String[] bits = OracleDialect.datetimeFormat( format.getFormat(), false, false ).result().split("\""); boolean first = true; for ( int i=0; i="; + } + + @Override + public void render( + SqlAppender sqlAppender, + List arguments, + SqlAstWalker walker) { + final int numberOfArguments = arguments.size(); + if ( numberOfArguments > 1 ) { + final int lastArgument = numberOfArguments - 1; + sqlAppender.appendSql( "case" ); + for ( int i = 0; i < lastArgument; i++ ) { + sqlAppender.appendSql( " when " ); + String separator = ""; + for ( int j = i + 1; j < numberOfArguments; j++ ) { + sqlAppender.appendSql( separator ); + arguments.get( i ).accept( walker ); + sqlAppender.appendSql( operator ); + arguments.get( j ).accept( walker ); + separator = " and "; + } + sqlAppender.appendSql( " then " ); + arguments.get( i ).accept( walker ); + } + sqlAppender.appendSql( " else " ); + arguments.get( lastArgument ).accept( walker ); + sqlAppender.appendSql( " end" ); + } + else { + arguments.get( 0 ).accept( walker ); + } + } + + @Override + public String getArgumentListSignature() { + return "(arg0[, arg1[, ...]])"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/InsertSubstringOverlayEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/InsertSubstringOverlayEmulation.java index 7cb77d01f7..3f33b678d7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/InsertSubstringOverlayEmulation.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/InsertSubstringOverlayEmulation.java @@ -8,7 +8,9 @@ import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType; import org.hibernate.query.BinaryArithmeticOperator; +import org.hibernate.query.ComparisonOperator; import org.hibernate.query.spi.QueryEngine; +import org.hibernate.query.sqm.SqmExpressable; import org.hibernate.query.sqm.function.AbstractSqmFunctionDescriptor; import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; import org.hibernate.query.sqm.function.SqmFunctionDescriptor; @@ -16,14 +18,18 @@ import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.expression.SqmBinaryArithmetic; +import org.hibernate.query.sqm.tree.expression.SqmCaseSearched; import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmLiteral; +import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate; import org.hibernate.type.BasicType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.spi.TypeConfiguration; import java.util.List; +import javax.persistence.criteria.Expression; + import static java.util.Arrays.asList; /** @@ -32,12 +38,15 @@ public class InsertSubstringOverlayEmulation extends AbstractSqmFunctionDescriptor { - public InsertSubstringOverlayEmulation() { + private final boolean strictSubstring; + + public InsertSubstringOverlayEmulation(boolean strictSubstring) { super( "overlay", StandardArgumentsValidators.between( 3, 4 ), StandardFunctionReturnTypeResolvers.invariant( StandardBasicTypes.STRING ) ); + this.strictSubstring = strictSubstring; } @Override @@ -66,6 +75,7 @@ protected SelfRenderingSqmFunction generateSqmFunctionExpression( ); } else { + SqmFunctionDescriptor lengthFunction = queryEngine.getSqmFunctionRegistry().findFunctionDescriptor("length"); SqmFunctionDescriptor substring = queryEngine.getSqmFunctionRegistry().findFunctionDescriptor("substring"); SqmFunctionDescriptor concat = queryEngine.getSqmFunctionRegistry().findFunctionDescriptor("concat"); SqmLiteral one = new SqmLiteral<>( 1, intType, queryEngine.getCriteriaBuilder() ); @@ -83,6 +93,30 @@ protected SelfRenderingSqmFunction generateSqmFunctionExpression( intType, queryEngine.getCriteriaBuilder() ); + SqmExpressable stringType = (SqmExpressable) impliedResultType; + SqmTypedNode restString = substring.generateSqmExpression( + asList( string, startPlusLength ), + impliedResultType, + queryEngine, + typeConfiguration + ); + if ( strictSubstring ) { + restString = (SqmTypedNode) new SqmCaseSearched<>( stringType, start.nodeBuilder() ) + .when( + new SqmComparisonPredicate( + startPlusLength, + ComparisonOperator.GREATER_THAN, + lengthFunction.generateSqmExpression( + asList( string ), + intType, + queryEngine, + typeConfiguration + ), + string.nodeBuilder() + ), + (Expression) new SqmLiteral<>( "", stringType, string.nodeBuilder() ) + ).otherwise( (Expression) restString ); + } return concat.generateSqmExpression( asList( substring.generateSqmExpression( @@ -92,12 +126,7 @@ protected SelfRenderingSqmFunction generateSqmFunctionExpression( typeConfiguration ), replacement, - substring.generateSqmExpression( - asList( string, startPlusLength ), - impliedResultType, - queryEngine, - typeConfiguration - ) + restString ), impliedResultType, queryEngine, diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/QuantifiedLeastGreatestEmulation.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/QuantifiedLeastGreatestEmulation.java new file mode 100644 index 0000000000..4e4e247418 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/QuantifiedLeastGreatestEmulation.java @@ -0,0 +1,74 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.dialect.function; + +import java.util.List; + +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.type.StandardBasicTypes; + +/** + * + * @author Christian Beikov + */ +public class QuantifiedLeastGreatestEmulation + extends AbstractSqmSelfRenderingFunctionDescriptor { + + private final String operator; + + public QuantifiedLeastGreatestEmulation(boolean least) { + super( + least ? "least" : "greatest", + StandardArgumentsValidators.min( 2 ), + StandardFunctionReturnTypeResolvers.useFirstNonNull() + ); + this.operator = least ? "<=" : ">="; + } + + @Override + public void render( + SqlAppender sqlAppender, + List arguments, + SqlAstWalker walker) { + final int numberOfArguments = arguments.size(); + if ( numberOfArguments > 1 ) { + final int lastArgument = numberOfArguments - 1; + sqlAppender.appendSql( "case" ); + for ( int i = 0; i < lastArgument; i++ ) { + sqlAppender.appendSql( " when " ); + arguments.get( i ).accept( walker ); + sqlAppender.appendSql( operator ); + sqlAppender.appendSql( " all(" ); + String separator = ""; + for ( int j = i + 1; j < numberOfArguments; j++ ) { + sqlAppender.appendSql( separator ); + arguments.get( j ).accept( walker ); + separator = ", "; + } + sqlAppender.appendSql( ") then " ); + arguments.get( i ).accept( walker ); + } + sqlAppender.appendSql( " else " ); + arguments.get( lastArgument ).accept( walker ); + sqlAppender.appendSql( " end" ); + } + else { + arguments.get( 0 ).accept( walker ); + } + } + + @Override + public String getArgumentListSignature() { + return "(arg0[, arg1[, ...]])"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLServerFormatFunction.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLServerFormatFunction.java new file mode 100644 index 0000000000..7cf49b3b19 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/SQLServerFormatFunction.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.dialect.function; + +import java.util.List; +import javax.persistence.TemporalType; + +import org.hibernate.dialect.SQLServerDialect; +import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; +import org.hibernate.query.sqm.produce.function.StandardArgumentsValidators; +import org.hibernate.query.sqm.produce.function.StandardFunctionReturnTypeResolvers; +import org.hibernate.sql.ast.SqlAstWalker; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.SqlAstNode; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.Format; +import org.hibernate.type.StandardBasicTypes; +import org.hibernate.type.spi.TypeConfiguration; + +/** + * SQL Server behaves strangely when the first argument to format is of the type time, so we cast to datetime. + * + * @author Christian Beikov + */ +public class SQLServerFormatFunction extends AbstractSqmSelfRenderingFunctionDescriptor { + + private final SQLServerDialect dialect; + + public SQLServerFormatFunction(SQLServerDialect dialect) { + super( + "format", + StandardArgumentsValidators.exactly( 2 ), + StandardFunctionReturnTypeResolvers.invariant( StandardBasicTypes.STRING ) + ); + this.dialect = dialect; + } + + @Override + public void render( + SqlAppender sqlAppender, + List arguments, + SqlAstWalker walker) { + final Expression datetime = (Expression) arguments.get(0); + final boolean isTime = TypeConfiguration.getSqlTemporalType( datetime.getExpressionType() ) == TemporalType.TIME; + final Format format = (Format) arguments.get(1); + + sqlAppender.appendSql("format("); + if ( isTime ) { + sqlAppender.appendSql("cast("); + datetime.accept( walker ); + sqlAppender.appendSql(" as datetime)"); + } + else { + datetime.accept( walker ); + } + sqlAppender.appendSql(",'"); + sqlAppender.appendSql( dialect.translateDatetimeFormat( format.getFormat() ) ); + sqlAppender.appendSql("')"); + } + + @Override + public String getArgumentListSignature() { + return "(datetime as pattern)"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java index 8e61a6928e..5cc70394be 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -240,11 +240,8 @@ public void setSqlTypeCode(Integer typeCode) { public String getSqlType(Dialect dialect, Mapping mapping) throws HibernateException { if ( sqlType == null ) { - final Size defaultSize = getColumnDefaultSize( dialect, mapping ); - - final Size size = getColumnSize( defaultSize ); try { - sqlType = dialect.getTypeName( getSqlTypeCode( mapping ), size ); + sqlType = dialect.getTypeName( getSqlTypeCode( mapping ), getColumnSize( dialect, mapping ) ); } catch (HibernateException cause) { throw new HibernateException( @@ -261,15 +258,7 @@ public String getSqlType(Dialect dialect, Mapping mapping) throws HibernateExcep return sqlType; } - private Size getColumnSize(Size defaultSize) { - final Integer columnPrecision = precision != null ? precision : defaultSize.getPrecision(); - final Integer columnScale = scale != null ? scale : defaultSize.getScale(); - final Long columnLength = length != null ? length : defaultSize.getLength(); - - return new Size( columnPrecision, columnScale, columnLength, null ); - } - - private Size getColumnDefaultSize(Dialect dialect, Mapping mapping) { + private Size getColumnSize(Dialect dialect, Mapping mapping) { Type type = getValue().getType(); if ( type instanceof EntityType ) { @@ -278,9 +267,12 @@ private Size getColumnDefaultSize(Dialect dialect, Mapping mapping) { if ( type instanceof ComponentType ) { type = getTypeForComponentValue( mapping, type, getTypeIndex() ); } - return dialect.getDefaultSizeStrategy().resolveDefaultSize( + return dialect.getSizeStrategy().resolveSize( ( (JdbcMapping) type ).getSqlTypeDescriptor(), - ( (JdbcMapping) type ).getJavaTypeDescriptor() + ( (JdbcMapping) type ).getJavaTypeDescriptor(), + precision, + scale, + length ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/JdbcMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/JdbcMapping.java index 84de493a34..d165bd910b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/JdbcMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/JdbcMapping.java @@ -6,6 +6,7 @@ */ package org.hibernate.metamodel.mapping; +import org.hibernate.query.CastType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -29,6 +30,9 @@ public interface JdbcMapping extends MappingType { */ SqlTypeDescriptor getSqlTypeDescriptor(); + default CastType getCastType() { + return getSqlTypeDescriptor().getCastType(); + } /** * The strategy for extracting values of this expressable diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDomainPath.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDomainPath.java index 286d07f1f7..8a55c88aea 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDomainPath.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDomainPath.java @@ -8,16 +8,16 @@ import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.SortOrder; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.SelectionMapping; import org.hibernate.metamodel.mapping.ordering.ast.DomainPath; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.select.QuerySpec; @@ -37,7 +37,6 @@ public void apply( String modelPartName, SortOrder sortOrder, SqlAstCreationState creationState) { - final SqlAstCreationContext creationContext = creationState.getCreationContext(); apply( getReferenceModelPart(), ast, @@ -45,9 +44,7 @@ public void apply( collation, modelPartName, sortOrder, - creationState, - creationContext.getSessionFactory(), - creationState.getSqlExpressionResolver() + creationState ); } @@ -58,9 +55,7 @@ public void apply( String collation, String modelPartName, SortOrder sortOrder, - SqlAstCreationState creationState, - SessionFactoryImplementor sessionFactory, - SqlExpressionResolver sqlExprResolver) { + SqlAstCreationState creationState) { if ( referenceModelPart instanceof BasicValuedModelPart ) { addSortSpecification( (BasicValuedModelPart) referenceModelPart, @@ -86,9 +81,7 @@ else if ( referenceModelPart instanceof EntityValuedModelPart ) { collation, modelPartName, sortOrder, - creationState, - sessionFactory, - sqlExprResolver + creationState ); } else if ( referenceModelPart instanceof EmbeddableValuedModelPart ) { @@ -99,9 +92,7 @@ else if ( referenceModelPart instanceof EmbeddableValuedModelPart ) { collation, modelPartName, sortOrder, - creationState, - sessionFactory, - sqlExprResolver + creationState ); } else { @@ -117,30 +108,18 @@ private void addSortSpecification( String collation, String modelPartName, SortOrder sortOrder, - SqlAstCreationState creationState, - SessionFactoryImplementor sessionFactory, - SqlExpressionResolver sqlExprResolver) { + SqlAstCreationState creationState) { if ( embeddableValuedModelPart.getFetchableName() .equals( modelPartName ) || ELEMENT_TOKEN.equals( modelPartName ) ) { embeddableValuedModelPart.forEachSelection( (columnIndex, selection) -> { - final TableReference tableReference = tableGroup.resolveTableReference( selection.getContainingTableExpression() ); - ast.addSortSpecification( - new SortSpecification( - sqlExprResolver.resolveSqlExpression( - SqlExpressionResolver.createColumnReferenceKey( - selection.getContainingTableExpression(), - selection.getSelectionExpression() - ), - sqlAstProcessingState -> new ColumnReference( - tableReference, - selection, - sessionFactory - ) - ), - collation, - sortOrder - ) + addSortSpecification( + selection, + ast, + tableGroup, + collation, + sortOrder, + creationState ); } ); @@ -160,24 +139,33 @@ private void addSortSpecification( } private void addSortSpecification( - BasicValuedModelPart basicValuedPart, + SelectionMapping selection, QuerySpec ast, TableGroup tableGroup, String collation, SortOrder sortOrder, SqlAstCreationState creationState) { - final TableReference tableReference = tableGroup.resolveTableReference( basicValuedPart.getContainingTableExpression() ); - - ast.addSortSpecification( - new SortSpecification( - new ColumnReference( - tableReference, - basicValuedPart, - creationState.getCreationContext().getSessionFactory() - ), - collation, - sortOrder + final TableReference tableReference = tableGroup.resolveTableReference( selection.getContainingTableExpression() ); + final Expression expression = creationState.getSqlExpressionResolver().resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( + selection.getContainingTableExpression(), + selection.getSelectionExpression() + ), + sqlAstProcessingState -> new ColumnReference( + tableReference, + selection, + creationState.getCreationContext().getSessionFactory() ) ); + // It makes no sense to order by an expression multiple times + // SQL Server even reports a query error in this case + if ( ast.hasSortSpecifications() ) { + for ( SortSpecification sortSpecification : ast.getSortSpecifications() ) { + if ( sortSpecification.getSortExpression() == expression ) { + return; + } + } + } + ast.addSortSpecification( new SortSpecification( expression, collation, sortOrder ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java index b201cb3cc3..1b7206b7af 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ordering/ast/ColumnReference.java @@ -15,6 +15,7 @@ import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReferenceJoin; @@ -70,27 +71,31 @@ public void apply( tableReference = getTableReference( tableGroup ); final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlExpressionResolver(); - - ast.addSortSpecification( - new SortSpecification( - sqlExpressionResolver.resolveSqlExpression( - SqlExpressionResolver.createColumnReferenceKey( tableReference, columnExpression ), - sqlAstProcessingState -> new org.hibernate.sql.ast.tree.expression.ColumnReference( - tableReference, - columnExpression, - isColumnExpressionFormula, - // because these ordering fragments are only ever part of the order-by clause, there - // is no need for the JdbcMapping - null, - null, - null, - creationState.getCreationContext().getSessionFactory() - ) - ), - collation, - sortOrder + final Expression expression = sqlExpressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( tableReference, columnExpression ), + sqlAstProcessingState -> new org.hibernate.sql.ast.tree.expression.ColumnReference( + tableReference, + columnExpression, + isColumnExpressionFormula, + // because these ordering fragments are only ever part of the order-by clause, there + // is no need for the JdbcMapping + null, + null, + null, + creationState.getCreationContext().getSessionFactory() ) ); + // It makes no sense to order by an expression multiple times + // SQL Server even reports a query error in this case + if ( ast.hasSortSpecifications() ) { + for ( SortSpecification sortSpecification : ast.getSortSpecifications() ) { + if ( sortSpecification.getSortExpression() == expression ) { + return; + } + } + } + + ast.addSortSpecification( new SortSpecification( expression, collation, sortOrder ) ); } TableReference getTableReference(TableGroup tableGroup) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/CastType.java b/hibernate-core/src/main/java/org/hibernate/query/CastType.java index 1bb71ded49..4e0a75f1ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/CastType.java +++ b/hibernate-core/src/main/java/org/hibernate/query/CastType.java @@ -6,13 +6,6 @@ */ package org.hibernate.query; -import org.hibernate.metamodel.mapping.JdbcMapping; -import org.hibernate.query.sqm.SqmExpressable; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.*; - /** * Defines the set of basic types which should be * accepted by the {@code cast()} function on every @@ -33,76 +26,25 @@ * @author Gavin King */ public enum CastType { - STRING(CastTypeKind.TEXT), - BOOLEAN(CastTypeKind.BOOLEAN), - INTEGER(CastTypeKind.NUMERIC), LONG(CastTypeKind.NUMERIC), FLOAT(CastTypeKind.NUMERIC), DOUBLE(CastTypeKind.NUMERIC), FIXED(CastTypeKind.NUMERIC), - DATE(CastTypeKind.TEMPORAL), TIME(CastTypeKind.TEMPORAL), TIMESTAMP(CastTypeKind.TEMPORAL), - OFFSET_TIMESTAMP(CastTypeKind.TEMPORAL), ZONE_TIMESTAMP(CastTypeKind.TEMPORAL), - NULL(null), - OTHER(null); + STRING, + BOOLEAN, INTEGER_BOOLEAN, YN_BOOLEAN, TF_BOOLEAN, + INTEGER, LONG, FLOAT, DOUBLE, FIXED, + DATE, TIME, TIMESTAMP, + OFFSET_TIMESTAMP, ZONE_TIMESTAMP, + NULL, + OTHER; - private final CastTypeKind kind; - - CastType(CastTypeKind kind) { - this.kind = kind; - } - - public CastTypeKind getKind() { - return kind; - } - - public static CastType from(Class javaClass) { - if (String.class.equals(javaClass)) { - return STRING; + public boolean isNumeric() { + switch (this) { + case INTEGER: + case LONG: + case INTEGER_BOOLEAN: + case FLOAT: + case DOUBLE: + case FIXED: + return true; + default: + return false; } - if (Boolean.class.equals(javaClass) - || boolean.class.equals(javaClass)) { - return BOOLEAN; - } - if (Integer.class.equals(javaClass) - || int.class.equals(javaClass)) { - return INTEGER; - } - if (Long.class.equals(javaClass) - || long.class.equals(javaClass)) { - return LONG; - } - if (Float.class.equals(javaClass) - || float.class.equals(javaClass)) { - return FLOAT; - } - if (Double.class.equals(javaClass) - || double.class.equals(javaClass)) { - return DOUBLE; - } - if (BigInteger.class.equals(javaClass)) { - return FIXED; - } - if (BigDecimal.class.equals(javaClass)) { - return FIXED; - } - if (LocalDate.class.equals(javaClass)) { - return DATE; - } - if (LocalTime.class.equals(javaClass)) { - return TIME; - } - if (LocalDateTime.class.equals(javaClass)) { - return TIMESTAMP; - } - if (OffsetDateTime.class.equals(javaClass)) { - return OFFSET_TIMESTAMP; - } - if (ZonedDateTime.class.equals(javaClass)) { - return ZONE_TIMESTAMP; - } - return OTHER; - } - - public static CastType from(JdbcMapping jdbcMapping) { - //TODO: I really don't like using the Java type - // here. Is there some way to base this - // off of the mapped SQL type? - return jdbcMapping == null ? NULL : from( jdbcMapping.getJavaTypeDescriptor().getJavaType() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/CastTypeKind.java b/hibernate-core/src/main/java/org/hibernate/query/CastTypeKind.java deleted file mode 100644 index e0d86961f1..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/query/CastTypeKind.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.query; - -/** - * The kind of type of a cast target. - * - * @see CastType - * - * @author Christian Beikov - */ -public enum CastTypeKind { - BOOLEAN, - NUMERIC, - TEMPORAL, - TEXT -} 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 7cf927e0e3..ac83a80e55 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 @@ -317,7 +317,7 @@ public SqmSelectStatement visitSelectStatement(HqlParser.SelectStatementContext ); try { - visitQueryExpression( queryExpressionContext ); + queryExpressionContext.accept( this ); } finally { processingStateStack.pop(); @@ -347,7 +347,7 @@ public SqmInsertStatement visitInsertStatement(HqlParser.InsertStatementContext processingStateStack.push( processingState ); try { - visitQueryExpression( queryExpressionContext ); + queryExpressionContext.accept( this ); final SqmCreationProcessingState stateFieldsProcessingState = new SqmCreationProcessingStateImpl( insertStatement, @@ -480,20 +480,28 @@ public SqmDeleteStatement visitDeleteStatement(HqlParser.DeleteStatementContext // Query spec @Override - public SqmQueryPart visitQueryExpression(HqlParser.QueryExpressionContext ctx) { - final SqmQueryPart queryPart = (SqmQueryPart) ctx.queryGroup().accept( this ); - visitQueryOrder( queryPart, ctx.queryOrder() ); + public SqmQueryPart visitSimpleQueryGroup(HqlParser.SimpleQueryGroupContext ctx) { + return (SqmQueryPart) ctx.simpleQueryExpression().accept( this ); + } + + @Override + public SqmQueryPart visitQuerySpecExpression(HqlParser.QuerySpecExpressionContext ctx) { + final List children = ctx.children; + final SqmQueryPart queryPart = visitQuerySpec( (HqlParser.QuerySpecContext) children.get( 0 ) ); + if ( children.size() > 1 ) { + visitQueryOrder( queryPart, (HqlParser.QueryOrderContext) children.get( 1 ) ); + } return queryPart; } @Override - public SqmQueryPart visitQuerySpecQueryGroup(HqlParser.QuerySpecQueryGroupContext ctx) { - return visitQuerySpec( ctx.querySpec() ); - } - - @Override - public SqmQueryPart visitNestedQueryGroup(HqlParser.NestedQueryGroupContext ctx) { - return (SqmQueryPart) ctx.queryGroup().accept( this ); + public SqmQueryPart visitNestedQueryExpression(HqlParser.NestedQueryExpressionContext ctx) { + final List children = ctx.children; + final SqmQueryPart queryPart = (SqmQueryPart) children.get( 1 ).accept( this ); + if ( children.size() > 3 ) { + visitQueryOrder( queryPart, (HqlParser.QueryOrderContext) children.get( 3 ) ); + } + return queryPart; } @Override @@ -515,20 +523,12 @@ public SqmQueryGroup visitSetQueryGroup(HqlParser.SetQueryGroupContext ctx) { setCurrentQueryPart( queryGroup ); final List firstSelections = firstQueryPart.getFirstQuerySpec().getSelectClause().getSelections(); final int firstSelectionSize = firstSelections.size(); - final ParseTree maybeOrderContext = children.get( 1 ); - int i; - if (maybeOrderContext instanceof HqlParser.QueryOrderContext ) { - visitQueryOrder( firstQueryPart, (HqlParser.QueryOrderContext) maybeOrderContext ); - i = 2; - } - else { - i = 1; - } final int size = children.size(); final SqmCreationProcessingState firstProcessingState = processingStateStack.pop(); - for ( ; i < size; i += 2 ) { + for ( int i = 1; i < size; i += 2 ) { final SetOperator operator = visitSetOperator( (HqlParser.SetOperatorContext) children.get( i ) ); - final ParseTree parseTree = children.get( i + 1 ); + final HqlParser.SimpleQueryExpressionContext simpleQueryCtx = + (HqlParser.SimpleQueryExpressionContext) children.get( i + 1 ); final List> queryParts; if ( queryGroup.getSetOperator() == null || queryGroup.getSetOperator() == operator ) { queryGroup.setSetOperator( operator ); @@ -554,15 +554,28 @@ public SqmQueryGroup visitSetQueryGroup(HqlParser.SetQueryGroupContext ctx) { this ) ); - if ( parseTree instanceof HqlParser.QuerySpecContext ) { + final List subChildren = simpleQueryCtx.children; + if ( subChildren.get( 0 ) instanceof HqlParser.QuerySpecContext ) { final SqmQuerySpec querySpec = new SqmQuerySpec<>( creationContext.getNodeBuilder() ); queryParts.add( querySpec ); - queryPart = visitQuerySpec( (HqlParser.QuerySpecContext) parseTree ); + queryPart = visitQuerySpecExpression( (HqlParser.QuerySpecExpressionContext) simpleQueryCtx ); } else { - queryPart = (SqmQueryPart) children.get( i + 2 ).accept( this ); - queryParts.add( queryPart ); - i += 2; + try { + final SqmSelectStatement selectStatement = new SqmSelectStatement( creationContext.getNodeBuilder() ); + processingStateStack.push( + new SqmQuerySpecCreationProcessingStateStandardImpl( + processingStateStack.getCurrent(), + selectStatement, + this + ) + ); + queryPart = visitNestedQueryExpression( (HqlParser.NestedQueryExpressionContext) simpleQueryCtx ); + queryParts.add( queryPart ); + } + finally { + processingStateStack.pop(); + } } } finally { @@ -3867,7 +3880,7 @@ public SqmSubQuery visitSubQuery(HqlParser.SubQueryContext ctx) { ); try { - visitQueryExpression( queryExpressionContext ); + queryExpressionContext.accept( this ); return subQuery; } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java index 977d6e7e6a..66c17ec0d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/ConcreteSqmSelectQueryPlan.java @@ -293,7 +293,7 @@ private static CacheableSqmInterpretation buildCacheableSqmInterpretation( interpretation.getSqlAst() ); - final Map, Map>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref( + final Map, Map>>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref( domainParameterXref, interpretation::getJdbcParamsBySqmParam ); @@ -326,13 +326,13 @@ T interpret( private static class CacheableSqmInterpretation { private final JdbcSelect jdbcSelect; private final FromClauseAccess tableGroupAccess; - private final Map, Map>> jdbcParamsXref; + private final Map, Map>>> jdbcParamsXref; private transient JdbcParameterBindings firstParameterBindings; CacheableSqmInterpretation( JdbcSelect jdbcSelect, FromClauseAccess tableGroupAccess, - Map, Map>> jdbcParamsXref, + Map, Map>>> jdbcParamsXref, JdbcParameterBindings firstParameterBindings) { this.jdbcSelect = jdbcSelect; this.tableGroupAccess = tableGroupAccess; @@ -348,7 +348,7 @@ FromClauseAccess getTableGroupAccess() { return tableGroupAccess; } - Map, Map>> getJdbcParamsXref() { + Map, Map>>> getJdbcParamsXref() { return jdbcParamsXref; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java index 0aa2d31603..d19c79ad35 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleDeleteQueryPlan.java @@ -50,7 +50,7 @@ public class SimpleDeleteQueryPlan implements NonSelectQueryPlan { private JdbcDelete jdbcDelete; private SqmTranslation sqmInterpretation; - private Map, Map>> jdbcParamsXref; + private Map, Map>>> jdbcParamsXref; public SimpleDeleteQueryPlan( EntityMappingType entityDescriptor, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java index f90d14d779..a8926ee707 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleInsertQueryPlan.java @@ -40,7 +40,7 @@ public class SimpleInsertQueryPlan implements NonSelectQueryPlan { private JdbcInsert jdbcInsert; private FromClauseAccess tableGroupAccess; - private Map, Map>> jdbcParamsXref; + private Map, Map>>> jdbcParamsXref; public SimpleInsertQueryPlan( SqmInsertStatement sqmInsert, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java index 4c29dc840a..c383a5c0c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SimpleUpdateQueryPlan.java @@ -40,7 +40,7 @@ public class SimpleUpdateQueryPlan implements NonSelectQueryPlan { private JdbcUpdate jdbcUpdate; private FromClauseAccess tableGroupAccess; - private Map, Map>> jdbcParamsXref; + private Map, Map>>> jdbcParamsXref; public SimpleUpdateQueryPlan( SqmUpdateStatement sqmUpdate, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java index b50bc9b18e..955783ac5d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java @@ -79,7 +79,7 @@ public static void verifyIsNonSelectStatement(SqmStatement sqm) { } } - public static Map, Map>> generateJdbcParamsXref( + public static Map, Map>>> generateJdbcParamsXref( DomainParameterXref domainParameterXref, JdbcParameterBySqmParameterAccess jdbcParameterBySqmParameterAccess) { if ( domainParameterXref == null || !domainParameterXref.hasParameters() ) { @@ -87,13 +87,13 @@ public static Map, Map, Map>> result = new IdentityHashMap<>( queryParameterCount ); + final Map, Map>>> result = new IdentityHashMap<>( queryParameterCount ); for ( Map.Entry, List> entry : domainParameterXref.getSqmParamByQueryParam().entrySet() ) { final QueryParameterImplementor queryParam = entry.getKey(); final List sqmParams = entry.getValue(); - final Map> sqmParamMap = result.computeIfAbsent( + final Map>> sqmParamMap = result.computeIfAbsent( queryParam, qp -> new IdentityHashMap<>( sqmParams.size() ) ); @@ -157,7 +157,7 @@ public static Map, Map, Map>> jdbcParamXref, + Map, Map>>> jdbcParamXref, MappingMetamodel domainModel, Function tableGroupLocator, SharedSessionContractImplementor session) { @@ -177,31 +177,43 @@ public static JdbcParameterBindings createJdbcParameterBindings( session.getFactory() ); - final Map> jdbcParamMap = jdbcParamXref.get( queryParam ); + final Map>> jdbcParamMap = jdbcParamXref.get( queryParam ); for ( SqmParameter sqmParameter : sqmParameters ) { - final List jdbcParams = jdbcParamMap.get( sqmParameter ); - - if ( ! domainParamBinding.isBound() ) { + final List> jdbcParamsBinds = jdbcParamMap.get( sqmParameter ); + if ( !domainParamBinding.isBound() ) { final MappingModelExpressable mappingExpressable = SqmMappingModelHelper.resolveMappingModelExpressable( sqmParameter, domainModel, tableGroupLocator ); - mappingExpressable.forEachJdbcType( - (position, jdbcType) -> { - jdbcParameterBindings.addBinding( - jdbcParams.get( position ), - new JdbcParameterBindingImpl( jdbcType, null ) - ); - } - ); + for ( int i = 0; i < jdbcParamsBinds.size(); i++ ) { + final List jdbcParams = jdbcParamsBinds.get( i ); + mappingExpressable.forEachJdbcType( + (position, jdbcType) -> { + jdbcParameterBindings.addBinding( + jdbcParams.get( position ), + new JdbcParameterBindingImpl( jdbcType, null ) + ); + } + ); + } } else if ( domainParamBinding.isMultiValued() ) { final Collection bindValues = domainParamBinding.getBindValues(); final Iterator valueItr = bindValues.iterator(); // the original SqmParameter is the one we are processing.. create a binding for it.. - createValueBindings( jdbcParameterBindings, domainParamBinding, parameterType, jdbcParams, valueItr.next(), session ); + for ( int i = 0; i < jdbcParamsBinds.size(); i++ ) { + final List jdbcParams = jdbcParamsBinds.get( i ); + createValueBindings( + jdbcParameterBindings, + domainParamBinding, + parameterType, + jdbcParams, + valueItr.next(), + session + ); + } // an then one for each of the expansions final List expansions = domainParameterXref.getExpansions( sqmParameter ); @@ -209,23 +221,45 @@ else if ( domainParamBinding.isMultiValued() ) { int expansionPosition = 0; while ( valueItr.hasNext() ) { final SqmParameter expansionSqmParam = expansions.get( expansionPosition++ ); - final List expansionJdbcParams = jdbcParamMap.get( expansionSqmParam ); - createValueBindings( jdbcParameterBindings, domainParamBinding, parameterType, expansionJdbcParams, valueItr.next(), session ); + final List> jdbcParamBinds = jdbcParamMap.get( expansionSqmParam ); + for ( int i = 0; i < jdbcParamBinds.size(); i++ ) { + List expansionJdbcParams = jdbcParamBinds.get( i ); + createValueBindings( + jdbcParameterBindings, + domainParamBinding, + parameterType, + expansionJdbcParams, + valueItr.next(), + session + ); + } } } else if ( domainParamBinding.getBindValue() == null ) { - assert jdbcParams != null; - for ( int i = 0; i < jdbcParams.size(); i++ ) { - final JdbcParameter jdbcParameter = jdbcParams.get( i ); - jdbcParameterBindings.addBinding( - jdbcParameter, - new JdbcParameterBindingImpl( null, null ) - ); + for ( int i = 0; i < jdbcParamsBinds.size(); i++ ) { + final List jdbcParams = jdbcParamsBinds.get( i ); + for ( int j = 0; j < jdbcParams.size(); j++ ) { + final JdbcParameter jdbcParameter = jdbcParams.get( j ); + jdbcParameterBindings.addBinding( + jdbcParameter, + new JdbcParameterBindingImpl( null, null ) + ); + } } } else { final Object bindValue = domainParamBinding.getBindValue(); - createValueBindings( jdbcParameterBindings, domainParamBinding, parameterType, jdbcParams, bindValue, session ); + for ( int i = 0; i < jdbcParamsBinds.size(); i++ ) { + final List jdbcParams = jdbcParamsBinds.get( i ); + createValueBindings( + jdbcParameterBindings, + domainParamBinding, + parameterType, + jdbcParams, + bindValue, + session + ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java index 94c9e86537..01dba6a907 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java @@ -198,8 +198,8 @@ protected Expression consumeSqmParameter(SqmParameter sqmParameter) { final Expression expression = super.consumeSqmParameter( sqmParameter ); - final List jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter ); - parameterResolutionConsumer.accept( sqmParameter, jdbcParameters ); + final List> jdbcParameters = getJdbcParamsBySqmParam().get( sqmParameter ); + parameterResolutionConsumer.accept( sqmParameter, jdbcParameters.get( jdbcParameters.size() - 1 ) ); return expression; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java index a30e9b0908..5a080cdd40 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteStrategy.java @@ -90,13 +90,6 @@ public CteStrategy( ); } - if ( !dialect.supportsRowValueConstructorSyntaxInInList() ) { - throw new UnsupportedOperationException( - getClass().getSimpleName() + - " can only be used with Dialects that support IN clause row-value expressions (for composite identifiers)" - ); - } - this.cteTable = new SqmCteTable( TABLE_NAME, rootDescriptor ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTableHelper.java index 8728cb5985..393a0944e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/IdTableHelper.java @@ -99,7 +99,7 @@ public void execute(Connection connection) { final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); try { - final String dropCommand = exporter.getSqlCreateCommand( idTable ); + final String dropCommand = exporter.getSqlDropCommand( idTable ); logStatement( dropCommand, jdbcServices ); try (Statement statement = connection.createStatement()) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java index 30b430c1ff..6d572caf0d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java @@ -125,7 +125,7 @@ public int execute(ExecutionContext executionContext) { final TableReference hierarchyRootTableReference = deletingTableGroup.resolveTableReference( hierarchyRootTableName ); assert hierarchyRootTableReference != null; - final Map> parameterResolutions; + final Map>> parameterResolutions; if ( domainParameterXref.getSqmParameterCount() == 0 ) { parameterResolutions = Collections.emptyMap(); } @@ -146,7 +146,10 @@ public int execute(ExecutionContext executionContext) { needsIdTableWrapper.set( true ); } }, - parameterResolutions::put + (sqmParameter, jdbcParameters) -> parameterResolutions.computeIfAbsent( + sqmParameter, + k -> new ArrayList<>( 1 ) + ).add( jdbcParameters ) ); final FilterPredicate filterPredicate = FilterHelper.createFilterPredicate( @@ -183,7 +186,7 @@ public int execute(ExecutionContext executionContext) { private int executeWithoutIdTable( Predicate suppliedPredicate, TableGroup tableGroup, - Map> restrictionSqmParameterResolutions, + Map>> restrictionSqmParameterResolutions, SqlExpressionResolver sqlExpressionResolver, ExecutionContext executionContext) { final EntityPersister rootEntityPersister; @@ -354,7 +357,7 @@ private static int executeSqlDelete( private int executeWithIdTable( Predicate predicate, TableGroup deletingTableGroup, - Map> restrictionSqmParameterResolutions, + Map>> restrictionSqmParameterResolutions, ExecutionContext executionContext) { final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings( executionContext.getQueryParameterBindings(), diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java index d914d619d7..110f0adbf0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/TableBasedUpdateHandler.java @@ -134,7 +134,7 @@ private ExecutionDelegate resolveDelegate(ExecutionContext executionContext) { final TableReference hierarchyRootTableReference = updatingTableGroup.resolveTableReference( hierarchyRootTableName ); assert hierarchyRootTableReference != null; - final Map> parameterResolutions; + final Map>> parameterResolutions; if ( domainParameterXref.getSqmParameterCount() == 0 ) { parameterResolutions = Collections.emptyMap(); } @@ -151,7 +151,10 @@ private ExecutionDelegate resolveDelegate(ExecutionContext executionContext) { converterDelegate.visitSetClause( getSqmDeleteOrUpdateStatement().getSetClause(), assignments::add, - parameterResolutions::put + (sqmParameter, jdbcParameters) -> parameterResolutions.computeIfAbsent( + sqmParameter, + k -> new ArrayList<>( 1 ) + ).add( jdbcParameters ) ); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -167,7 +170,10 @@ private ExecutionDelegate resolveDelegate(ExecutionContext executionContext) { predicate = converterDelegate.visitWhereClause( whereClause, columnReference -> {}, - parameterResolutions::put + (sqmParameter, jdbcParameters) -> parameterResolutions.computeIfAbsent( + sqmParameter, + k -> new ArrayList<>( 1 ) + ).add( jdbcParameters ) ); assert predicate != null; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java index e26f2d4b66..19cb3002a0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/UpdateExecutionDelegate.java @@ -78,7 +78,7 @@ public UpdateExecutionDelegate( Map tableReferenceByAlias, List assignments, Predicate suppliedPredicate, - Map> parameterResolutions, + Map>> parameterResolutions, ExecutionContext executionContext) { this.sqmUpdate = sqmUpdate; this.sqmConverter = sqmConverter; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/JdbcParameterBySqmParameterAccess.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/JdbcParameterBySqmParameterAccess.java index e8926d3ed5..e6a16da1d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/JdbcParameterBySqmParameterAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/JdbcParameterBySqmParameterAccess.java @@ -21,5 +21,5 @@ public interface JdbcParameterBySqmParameterAccess { /** * The mapping between an SqmParameter and all of its JDBC parameters */ - Map> getJdbcParamsBySqmParam(); + Map>> getJdbcParamsBySqmParam(); } 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 9419f75be2..b6c8a612be 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 @@ -26,6 +26,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.boot.model.process.internal.InferredBasicValueResolver; import org.hibernate.dialect.function.TimestampaddFunction; import org.hibernate.dialect.function.TimestampdiffFunction; import org.hibernate.engine.FetchTiming; @@ -41,9 +42,11 @@ import org.hibernate.metamodel.CollectionClassification; import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.mapping.Association; +import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; @@ -265,8 +268,10 @@ import org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiation; import org.hibernate.sql.results.internal.SqlSelectionImpl; import org.hibernate.sql.results.internal.StandardEntityGraphTraversalStateImpl; +import org.hibernate.type.BasicType; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; +import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; import org.hibernate.type.spi.TypeConfiguration; import org.jboss.logging.Logger; @@ -284,7 +289,7 @@ * @author Steve Ebersole */ public abstract class BaseSqmToSqlAstConverter extends BaseSemanticQueryWalker - implements SqmTranslator, DomainResultCreationState { + implements SqmTranslator, DomainResultCreationState, SqlTypeDescriptorIndicators { private static final Logger log = Logger.getLogger( BaseSqmToSqlAstConverter.class ); @@ -402,7 +407,20 @@ protected void popProcessingStateStack() { protected SqmStatement getStatement() { return statement; } - + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // SqlTypeDescriptorIndicators + + @Override + public TypeConfiguration getTypeConfiguration() { + return creationContext.getSessionFactory().getTypeConfiguration(); + } + + @Override + public int getPreferredSqlTypeCodeForBoolean() { + return creationContext.getSessionFactory().getSessionFactoryOptions().getPreferredSqlTypeCodeForBoolean(); + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // FromClauseAccess @@ -578,7 +596,7 @@ public UpdateStatement visitUpdateStatement(SqmUpdateStatement sqmStatement) { @Override public List visitSetClause(SqmSetClause setClause) { - final List assignments = new ArrayList<>(); + final List assignments = new ArrayList<>( setClause.getAssignments().size() ); for ( SqmAssignment sqmAssignment : setClause.getAssignments() ) { final List targetColumnReferences = new ArrayList<>(); @@ -663,14 +681,15 @@ public Expression resolveSqlExpression( } ); - getJdbcParamsBySqmParam().put( sqmParameter, jdbcParametersForSqm ); + getJdbcParamsBySqmParam().computeIfAbsent( sqmParameter, k -> new ArrayList<>( 1 ) ) + .add( jdbcParametersForSqm ); } else { - final MappingMetamodel domainModel = getCreationContext().getDomainModel(); final Expression valueExpression = (Expression) sqmAssignment.getValue().accept( this ); - final int valueExprJdbcCount = valueExpression.getExpressionType().getJdbcTypeCount(); - final int assignedPathJdbcCount = assignedPathInterpretation.getExpressionType().getJdbcTypeCount(); + final int valueExprJdbcCount = getKeyExpressable( valueExpression.getExpressionType() ).getJdbcTypeCount(); + final int assignedPathJdbcCount = getKeyExpressable( assignedPathInterpretation.getExpressionType() ) + .getJdbcTypeCount(); if ( valueExprJdbcCount != assignedPathJdbcCount ) { SqlTreeCreationLogger.LOGGER.debugf( @@ -2021,24 +2040,50 @@ public SqmPathInterpretation visitPluralValuedPath(SqmPluralValuedSimplePath @Override public Expression visitLiteral(SqmLiteral literal) { if ( literal instanceof SqmLiteralNull ) { - return new NullnessLiteral( inferableTypeAccessStack.getCurrent().get() ); + final MappingModelExpressable mappingModelExpressable = inferableTypeAccessStack.getCurrent().get(); + if ( mappingModelExpressable instanceof BasicValuedMapping ) { + return new NullnessLiteral( mappingModelExpressable ); + } + final MappingModelExpressable keyExpressable = getKeyExpressable( mappingModelExpressable ); + final List expressions = new ArrayList<>( keyExpressable.getJdbcTypeCount() ); + keyExpressable.forEachJdbcType( + (index, jdbcMapping) -> expressions.add( + new QueryLiteral<>( + null, + (BasicValuedMapping) jdbcMapping + ) + ) + ); + return new SqlTuple( expressions, mappingModelExpressable ); + } + MappingModelExpressable expressable = SqmMappingModelHelper.resolveMappingModelExpressable( + literal, + getCreationContext().getDomainModel(), + getFromClauseAccess()::findTableGroup + ); + if ( expressable instanceof BasicType ) { + expressable = InferredBasicValueResolver.resolveSqlTypeIndicators( this, (BasicType) expressable ); } - return new QueryLiteral<>( literal.getLiteralValue(), - (BasicValuedMapping) SqmMappingModelHelper.resolveMappingModelExpressable( - literal, - getCreationContext().getDomainModel(), - getFromClauseAccess()::findTableGroup - ) + (BasicValuedMapping) expressable ); } - private final Map> jdbcParamsBySqmParam = new IdentityHashMap<>(); + private MappingModelExpressable getKeyExpressable(MappingModelExpressable mappingModelExpressable) { + if ( mappingModelExpressable instanceof EntityAssociationMapping ) { + return ( (EntityAssociationMapping) mappingModelExpressable ).getKeyTargetMatchPart(); + } + else { + return mappingModelExpressable; + } + } + + private final Map>> jdbcParamsBySqmParam = new IdentityHashMap<>(); private final JdbcParameters jdbcParameters = new JdbcParametersImpl(); @Override - public Map> getJdbcParamsBySqmParam() { + public Map>> getJdbcParamsBySqmParam() { return jdbcParamsBySqmParam; } @@ -2059,7 +2104,8 @@ protected Expression consumeSqmParameter(SqmParameter sqmParameter) { ); this.jdbcParameters.addParameters( jdbcParametersForSqm ); - this.jdbcParamsBySqmParam.put( sqmParameter, jdbcParametersForSqm ); + this.jdbcParamsBySqmParam.computeIfAbsent( sqmParameter, k -> new ArrayList<>( 1 ) ) + .add( jdbcParametersForSqm ); final QueryParameterImplementor queryParameter = domainParameterXref.getQueryParameter( sqmParameter ); final QueryParameterBinding binding = domainParameterBindings.getBinding( queryParameter ); @@ -2268,8 +2314,12 @@ public Object visitTrimSpecification(SqmTrimSpecification specification) { public Object visitCastTarget(SqmCastTarget target) { shallownessStack.push( Shallowness.FUNCTION ); try { + BasicValuedMapping targetType = (BasicValuedMapping) target.getType(); + if ( targetType instanceof BasicType ) { + targetType = InferredBasicValueResolver.resolveSqlTypeIndicators( this, (BasicType) targetType ); + } return new CastTarget( - (BasicValuedMapping) target.getType(), + targetType, target.getLength(), target.getPrecision(), target.getScale() @@ -2818,9 +2868,9 @@ private Object transformDurationArithmetic(SqmBinaryArithmetic expression) { if ( type instanceof SqmExpressable ) { adjustedTimestampType = (SqmExpressable) type; } -// else if (type instanceof BasicValuedMapping) { -// adjustedTimestampType = ((BasicValuedMapping) type).getBasicType(); -// } + else if (type instanceof AttributeMapping ) { + adjustedTimestampType = (SqmExpressable) ( (AttributeMapping) type ).getMappedType(); + } else { // else we know it has not been transformed adjustedTimestampType = expression.getLeftHandOperand().getNodeType(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslation.java index 9ffe08ad71..c6d98d5d99 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/SqmTranslation.java @@ -22,7 +22,7 @@ */ public interface SqmTranslation { T getSqlAst(); - Map> getJdbcParamsBySqmParam(); + Map>> getJdbcParamsBySqmParam(); SqlExpressionResolver getSqlExpressionResolver(); FromClauseAccess getFromClauseAccess(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslation.java index d59586fc7f..0d7968a459 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/StandardSqmTranslation.java @@ -21,13 +21,13 @@ public class StandardSqmTranslation implements SqmTranslation { private final T sqlAst; - private final Map> jdbcParamMap; + private final Map>> jdbcParamMap; private final SqlExpressionResolver sqlExpressionResolver; private final FromClauseAccess fromClauseAccess; public StandardSqmTranslation( T sqlAst, - Map> jdbcParamMap, + Map>> jdbcParamMap, SqlExpressionResolver sqlExpressionResolver, FromClauseAccess fromClauseAccess) { this.sqlAst = sqlAst; @@ -42,7 +42,7 @@ public T getSqlAst() { } @Override - public Map> getJdbcParamsBySqmParam() { + public Map>> getJdbcParamsBySqmParam() { return jdbcParamMap; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/Template.java b/hibernate-core/src/main/java/org/hibernate/sql/Template.java index 28ce5bc199..368296a493 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/Template.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/Template.java @@ -62,6 +62,9 @@ public final class Template { KEYWORDS.add("all"); KEYWORDS.add("union"); KEYWORDS.add("minus"); + KEYWORDS.add("except"); + KEYWORDS.add("intersect"); + KEYWORDS.add("partition"); BEFORE_TABLE_KEYWORDS.add("from"); BEFORE_TABLE_KEYWORDS.add("join"); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java index 4c50dbd38c..1902f575c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/SqlAstTranslator.java @@ -11,12 +11,11 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.exec.spi.JdbcParameterBindings; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; /** * @author Steve Ebersole */ -public interface SqlAstTranslator extends SqlAstWalker, SqlTypeDescriptorIndicators { +public interface SqlAstTranslator extends SqlAstWalker { /** * Not the best spot for this. Its the table names collected while walking the SQL AST. * Its ok here because the translator is consider a one-time-use. It just needs to be called diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java index f64b31cb7e..bf5afe337c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstWalker.java @@ -121,16 +121,13 @@ import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.sql.JdbcLiteralFormatter; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; -import org.hibernate.type.spi.TypeConfiguration; import static org.hibernate.query.TemporalUnit.NANOSECOND; /** * @author Steve Ebersole */ -public abstract class AbstractSqlAstWalker - implements SqlAstWalker, SqlTypeDescriptorIndicators, SqlAppender { +public abstract class AbstractSqlAstWalker implements SqlAstWalker, SqlAppender { private static final QueryLiteral ONE_LITERAL = new QueryLiteral<>( 1, IntegerType.INSTANCE ); @@ -150,6 +147,7 @@ public abstract class AbstractSqlAstWalker private final Dialect dialect; private String dmlTargetTableAlias; + private boolean needsSelectAliases; private QueryPart queryPartForRowNumbering; private int queryPartForRowNumberingAliasCounter; private int queryGroupAliasCounter; @@ -468,35 +466,7 @@ protected void visitInsertStatementOnly(InsertStatement statement) { statement.getSourceSelectStatement().accept( this ); } else { - appendSql("values"); - boolean firstTuple = true; - final Stack clauseStack = getClauseStack(); - try { - clauseStack.push( Clause.VALUES ); - for ( Values values : statement.getValuesList() ) { - if ( firstTuple ) { - firstTuple = false; - } - else { - appendSql( ", " ); - } - appendSql( " (" ); - boolean firstExpr = true; - for ( Expression expression : values.getExpressions() ) { - if ( firstExpr ) { - firstExpr = false; - } - else { - appendSql( ", " ); - } - expression.accept( this ); - } - appendSql( ")" ); - } - } - finally { - clauseStack.pop(); - } + visitValuesList( statement.getValuesList() ); } visitReturningColumns( statement ); } @@ -504,6 +474,38 @@ protected void visitInsertStatementOnly(InsertStatement statement) { private void renderImplicitTargetColumnSpec() { } + protected void visitValuesList(List valuesList) { + appendSql("values"); + boolean firstTuple = true; + final Stack clauseStack = getClauseStack(); + try { + clauseStack.push( Clause.VALUES ); + for ( Values values : valuesList ) { + if ( firstTuple ) { + firstTuple = false; + } + else { + appendSql( ", " ); + } + appendSql( " (" ); + boolean firstExpr = true; + for ( Expression expression : values.getExpressions() ) { + if ( firstExpr ) { + firstExpr = false; + } + else { + appendSql( ", " ); + } + expression.accept( this ); + } + appendSql( ")" ); + } + } + finally { + clauseStack.pop(); + } + } + protected void visitReturningColumns(MutationStatement mutationStatement) { final List returningColumns = mutationStatement.getReturningColumns(); final int size = returningColumns.size(); @@ -623,13 +625,17 @@ protected void renderCycleClause(CteStatement cte) { @Override public void visitQueryGroup(QueryGroup queryGroup) { final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; + final boolean needsSelectAliases = this.needsSelectAliases; try { String queryGroupAlias = null; - if ( queryPartForRowNumbering != queryPartStack.getCurrent() ) { + final QueryPart currentQueryPart = queryPartStack.getCurrent(); + if ( currentQueryPart != null && queryPartForRowNumbering != currentQueryPart ) { this.queryPartForRowNumbering = null; + this.needsSelectAliases = false; } // If we do row counting for this query group, the wrapper select is added by the caller if ( queryPartForRowNumbering != queryGroup && !queryGroup.isRoot() ) { + this.needsSelectAliases = true; queryGroupAlias = "grp_" + queryGroupAliasCounter + "_"; queryGroupAliasCounter++; appendSql( "select " ); @@ -656,17 +662,39 @@ public void visitQueryGroup(QueryGroup queryGroup) { finally { queryPartStack.pop(); this.queryPartForRowNumbering = queryPartForRowNumbering; + this.needsSelectAliases = needsSelectAliases; } } @Override public void visitQuerySpec(QuerySpec querySpec) { final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; + final boolean needsSelectAliases = this.needsSelectAliases; try { - if ( queryPartForRowNumbering != queryPartStack.getCurrent() ) { + final QueryPart currentQueryPart = queryPartStack.getCurrent(); + if ( currentQueryPart != null && queryPartForRowNumbering != currentQueryPart ) { this.queryPartForRowNumbering = null; } - final boolean needsParenthesis = !querySpec.isRoot() && !( queryPartStack.getCurrent() instanceof QueryGroup ); + String queryGroupAlias = ""; + final boolean needsParenthesis; + if ( currentQueryPart instanceof QueryGroup ) { + // We always need query wrapping if we are in a query group and the query part has a fetch clause + if ( needsParenthesis = querySpec.hasOffsetOrFetchClause() ) { + // If the parent is a query group with a fetch clause, we must use an alias + // Some DBMS don't support grouping query expressions and need a select wrapper + if ( !supportsSimpleQueryGrouping() || currentQueryPart.hasOffsetOrFetchClause() ) { + this.needsSelectAliases = true; + queryGroupAlias = " grp_" + queryGroupAliasCounter + "_"; + queryGroupAliasCounter++; + appendSql( "select" ); + appendSql( queryGroupAlias ); + appendSql( ".* from " ); + } + } + } + else { + needsParenthesis = !querySpec.isRoot(); + } queryPartStack.push( querySpec ); if ( needsParenthesis ) { appendSql( "(" ); @@ -681,14 +709,20 @@ public void visitQuerySpec(QuerySpec querySpec) { if ( needsParenthesis ) { appendSql( ")" ); + appendSql( queryGroupAlias ); } } finally { queryPartStack.pop(); this.queryPartForRowNumbering = queryPartForRowNumbering; + this.needsSelectAliases = needsSelectAliases; } } + protected boolean supportsSimpleQueryGrouping() { + return true; + } + protected final void visitWhereClause(QuerySpec querySpec) { final Predicate whereClauseRestrictions = querySpec.getWhereClauseRestrictions(); if ( whereClauseRestrictions != null && !whereClauseRestrictions.isEmpty() ) { @@ -802,8 +836,12 @@ protected void renderPartitionItem(Expression expression) { appendSql( "()" ); break; case SUBQUERY: - appendSql( "(select 1 " ); - appendSql( dialect.getFromDual() ); + appendSql( "(select 1" ); + final String fromDual = dialect.getFromDual(); + if ( !fromDual.isEmpty() ) { + appendSql( " " ); + appendSql( fromDual ); + } appendSql( ')' ); break; case COLUMN_REFERENCE: @@ -965,14 +1003,14 @@ protected void emulateTupleComparison( } } - private void renderExpressionsAsSubquery(final List expressions) { + protected void renderExpressionsAsSubquery(final List expressions) { clauseStack.push( Clause.SELECT ); try { appendSql( "select " ); renderCommaSeparated( expressions ); - String fromDual = dialect.getFromDual(); + final String fromDual = dialect.getFromDual(); if ( !fromDual.isEmpty() ) { appendSql( " " ); appendSql( fromDual ); @@ -1634,8 +1672,10 @@ protected void emulateFetchOffsetWithWindowFunctions( FetchClauseType fetchClauseType, boolean emulateFetchClause) { final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; + final boolean needsSelectAliases = this.needsSelectAliases; try { this.queryPartForRowNumbering = queryPart; + this.needsSelectAliases = true; final String alias = "r_" + queryPartForRowNumberingAliasCounter + "_"; queryPartForRowNumberingAliasCounter++; appendSql( "select " ); @@ -1736,6 +1776,7 @@ protected void emulateFetchOffsetWithWindowFunctions( } finally { this.queryPartForRowNumbering = queryPartForRowNumbering; + this.needsSelectAliases = needsSelectAliases; } } @@ -1761,7 +1802,51 @@ public void visitSelectClause(SelectClause selectClause) { protected void visitSqlSelections(SelectClause selectClause) { final List sqlSelections = selectClause.getSqlSelections(); final int size = sqlSelections.size(); - if ( queryPartForRowNumbering == null ) { + if ( needsSelectAliases ) { + String separator = NO_SEPARATOR; + for ( int i = 0; i < size; i++ ) { + final SqlSelection sqlSelection = sqlSelections.get( i ); + appendSql( separator ); + sqlSelection.accept( this ); + appendSql( " c" ); + appendSql( Integer.toString( i ) ); + separator = COMA_SEPARATOR; + } + if ( queryPartForRowNumbering != null ) { + final FetchClauseType fetchClauseType = getFetchClauseTypeForRowNumbering( queryPartForRowNumbering ); + if ( fetchClauseType != null ) { + appendSql( separator ); + switch ( fetchClauseType ) { + case PERCENT_ONLY: + appendSql( "count(*) over () cnt," ); + case ROWS_ONLY: + renderRowNumber( selectClause, queryPartForRowNumbering ); + appendSql( " rn " ); + break; + case PERCENT_WITH_TIES: + appendSql( "count(*) over () cnt," ); + case ROWS_WITH_TIES: + if ( queryPartForRowNumbering.getOffsetClauseExpression() != null ) { + renderRowNumber( selectClause, queryPartForRowNumbering ); + appendSql( " rn, " ); + } + if ( selectClause.isDistinct() ) { + appendSql( "dense_rank()" ); + } + else { + appendSql( "rank()" ); + } + visitOverClause( + Collections.emptyList(), + getSortSpecificationsRowNumbering( selectClause, queryPartForRowNumbering ) + ); + appendSql( " rnk" ); + break; + } + } + } + } + else { String separator = NO_SEPARATOR; for ( int i = 0; i < size; i++ ) { final SqlSelection sqlSelection = sqlSelections.get( i ); @@ -1770,43 +1855,6 @@ protected void visitSqlSelections(SelectClause selectClause) { separator = COMA_SEPARATOR; } } - else { - for ( int i = 0; i < size; i++ ) { - final SqlSelection sqlSelection = sqlSelections.get( i ); - sqlSelection.accept( this ); - appendSql( " c" ); - appendSql( Integer.toString( i ) ); - appendSql( COMA_SEPARATOR ); - } - - switch ( getFetchClauseTypeForRowNumbering( queryPartForRowNumbering ) ) { - case PERCENT_ONLY: - appendSql( "count(*) over () cnt," ); - case ROWS_ONLY: - renderRowNumber( selectClause, queryPartForRowNumbering ); - appendSql( " rn " ); - break; - case PERCENT_WITH_TIES: - appendSql( "count(*) over () cnt," ); - case ROWS_WITH_TIES: - if ( queryPartForRowNumbering.getOffsetClauseExpression() != null ) { - renderRowNumber( selectClause, queryPartForRowNumbering ); - appendSql( " rn, " ); - } - if ( selectClause.isDistinct() ) { - appendSql( "dense_rank()" ); - } - else { - appendSql( "rank()" ); - } - visitOverClause( - Collections.emptyList(), - getSortSpecificationsRowNumbering( selectClause, queryPartForRowNumbering ) - ); - appendSql( " rnk" ); - break; - } - } } protected FetchClauseType getFetchClauseTypeForRowNumbering(QueryPart queryPartForRowNumbering) { @@ -2098,7 +2146,7 @@ public void visitColumnReference(ColumnReference columnReference) { @Override public void visitExtractUnit(ExtractUnit extractUnit) { - appendSql( sessionFactory.getJdbcServices().getDialect().translateExtractField( extractUnit.getUnit() ) ); + appendSql( getDialect().translateExtractField( extractUnit.getUnit() ) ); } @Override @@ -2108,7 +2156,7 @@ public void visitDurationUnit(DurationUnit unit) { @Override public void visitFormat(Format format) { - final String dialectFormat = sessionFactory.getJdbcServices().getDialect().translateDatetimeFormat( format.getFormat() ); + final String dialectFormat = getDialect().translateDatetimeFormat( format.getFormat() ); appendSql( "'" ); appendSql( dialectFormat ); appendSql( "'" ); @@ -2846,8 +2894,10 @@ protected void emulateTupleSubQueryPredicate( } final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; + final boolean needsSelectAliases = this.needsSelectAliases; try { this.queryPartForRowNumbering = null; + this.needsSelectAliases = false; queryPartStack.push( subQuery ); appendSql( "exists (select 1" ); visitFromClause( subQuery.getFromClause() ); @@ -2904,6 +2954,7 @@ protected void emulateTupleSubQueryPredicate( finally { queryPartStack.pop(); this.queryPartForRowNumbering = queryPartForRowNumbering; + this.needsSelectAliases = needsSelectAliases; } } else { @@ -2931,8 +2982,10 @@ protected void emulateQuantifiedTupleSubQueryPredicate( appendSql( " " ); final QueryPart queryPartForRowNumbering = this.queryPartForRowNumbering; + final boolean needsSelectAliases = this.needsSelectAliases; try { this.queryPartForRowNumbering = null; + this.needsSelectAliases = false; queryPartStack.push( subQuery ); appendSql( "(" ); visitSelectClause( subQuery.getSelectClause() ); @@ -2964,6 +3017,7 @@ protected void emulateQuantifiedTupleSubQueryPredicate( finally { queryPartStack.pop(); this.queryPartForRowNumbering = queryPartForRowNumbering; + this.needsSelectAliases = needsSelectAliases; } } else { @@ -3198,22 +3252,4 @@ else if ( dialect.supportsRowValueConstructorSyntax() ) { } } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // JdbcRecommendedSqlTypeMappingContext - - @Override - public boolean isNationalized() { - return false; - } - - @Override - public boolean isLob() { - return false; - } - - @Override - public TypeConfiguration getTypeConfiguration() { - return getSessionFactory().getTypeConfiguration(); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlSelectAstWalker.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlSelectAstWalker.java deleted file mode 100644 index 538bbe3f51..0000000000 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlSelectAstWalker.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later - * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.sql.ast.spi; - -import org.hibernate.sql.ast.SqlAstWalker; - -/** - * @author Steve Ebersole - * @author Andrea Boriero - */ -public interface SqlSelectAstWalker extends SqlAstWalker { -} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java index 767b1d572f..959b7e30fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java @@ -100,7 +100,7 @@ public ScrollableResultsImplementor scroll( rowTransformer, (sql) -> executionContext.getSession().getJdbcCoordinator().getStatementPreparer().prepareQueryStatement( sql, - true, + false, scrollMode ), ScrollableResultsConsumer.instance() diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java index c51a8ddb02..cc83ddb477 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java @@ -155,7 +155,7 @@ private void executeQuery() { // For dialects that don't support an offset clause final int rowsToSkip; - if ( !jdbcSelect.usesLimitParameters() && limit != null && limit.getFirstRow() != null && !limitHandler.supportsOffset() ) { + if ( !jdbcSelect.usesLimitParameters() && limit != null && limit.getFirstRow() != null && !limitHandler.supportsLimitOffset() ) { rowsToSkip = limit.getFirstRow(); } else { diff --git a/hibernate-core/src/main/java/org/hibernate/type/NumericBooleanType.java b/hibernate-core/src/main/java/org/hibernate/type/NumericBooleanType.java index ffc8090c79..efb173432a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/NumericBooleanType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/NumericBooleanType.java @@ -9,6 +9,7 @@ import java.io.Serializable; import org.hibernate.dialect.Dialect; +import org.hibernate.query.CastType; import org.hibernate.type.descriptor.java.BooleanTypeDescriptor; import org.hibernate.type.descriptor.sql.IntegerTypeDescriptor; @@ -46,4 +47,8 @@ public Boolean stringToObject(String string) { public String objectToSQLString(Boolean value, Dialect dialect) { return value ? "1" : "0"; } + @Override + public CastType getCastType() { + return CastType.INTEGER_BOOLEAN; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/OffsetDateTimeType.java b/hibernate-core/src/main/java/org/hibernate/type/OffsetDateTimeType.java index 724b346dde..f8aa980ad9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/OffsetDateTimeType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/OffsetDateTimeType.java @@ -14,6 +14,7 @@ import org.hibernate.QueryException; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.model.domain.AllowableTemporalParameterType; +import org.hibernate.query.CastType; import org.hibernate.type.descriptor.java.OffsetDateTimeJavaDescriptor; import org.hibernate.type.descriptor.sql.TimestampWithTimeZoneDescriptor; import org.hibernate.type.spi.TypeConfiguration; @@ -79,4 +80,9 @@ public AllowableTemporalParameterType resolveTemporalPrecision( } } } + + @Override + public CastType getCastType() { + return CastType.OFFSET_TIMESTAMP; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/TrueFalseType.java b/hibernate-core/src/main/java/org/hibernate/type/TrueFalseType.java index 23331a0756..4d86c2ab27 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/TrueFalseType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/TrueFalseType.java @@ -9,10 +9,9 @@ import java.io.Serializable; import org.hibernate.dialect.Dialect; +import org.hibernate.query.CastType; import org.hibernate.type.descriptor.java.BooleanTypeDescriptor; import org.hibernate.type.descriptor.sql.CharTypeDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; /** * A type that maps between {@link java.sql.Types#CHAR CHAR(1)} and {@link Boolean} (using 'T' and 'F') @@ -22,7 +21,7 @@ */ public class TrueFalseType extends AbstractSingleColumnStandardBasicType - implements PrimitiveType, DiscriminatorType, SqlTypeDescriptorIndicatorCapable { + implements PrimitiveType, DiscriminatorType { public static final TrueFalseType INSTANCE = new TrueFalseType(); @@ -51,18 +50,7 @@ public String objectToSQLString(Boolean value, Dialect dialect) throws Exception } @Override - public BasicType resolveIndicatedType(SqlTypeDescriptorIndicators indicators) { - if ( indicators.getPreferredSqlTypeCodeForBoolean() != getSqlTypeDescriptor().getJdbcTypeCode() ) { - final SqlTypeDescriptor sqlTypeDescriptor = indicators.getTypeConfiguration() - .getSqlTypeDescriptorRegistry() - .getDescriptor( indicators.getPreferredSqlTypeCodeForBoolean() ); - //noinspection unchecked - return (BasicType) indicators.getTypeConfiguration() - .getBasicTypeRegistry() - .resolve( getJavaTypeDescriptor(), sqlTypeDescriptor ); - } - - //noinspection unchecked - return (BasicType) this; + public CastType getCastType() { + return CastType.TF_BOOLEAN; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/YesNoType.java b/hibernate-core/src/main/java/org/hibernate/type/YesNoType.java index ca370d791b..84bd0b45e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/YesNoType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/YesNoType.java @@ -9,10 +9,9 @@ import java.io.Serializable; import org.hibernate.dialect.Dialect; +import org.hibernate.query.CastType; import org.hibernate.type.descriptor.java.BooleanTypeDescriptor; import org.hibernate.type.descriptor.sql.CharTypeDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; -import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; /** * A type that maps between {@link java.sql.Types#CHAR CHAR(1)} and {@link Boolean} (using 'Y' and 'N') @@ -22,7 +21,7 @@ */ public class YesNoType extends AbstractSingleColumnStandardBasicType - implements PrimitiveType, DiscriminatorType, SqlTypeDescriptorIndicatorCapable { + implements PrimitiveType, DiscriminatorType { public static final YesNoType INSTANCE = new YesNoType(); @@ -51,18 +50,7 @@ public String objectToSQLString(Boolean value, Dialect dialect) throws Exception } @Override - public BasicType resolveIndicatedType(SqlTypeDescriptorIndicators indicators) { - if ( indicators.getPreferredSqlTypeCodeForBoolean() != getSqlTypeDescriptor().getJdbcTypeCode() ) { - final SqlTypeDescriptor sqlTypeDescriptor = indicators.getTypeConfiguration() - .getSqlTypeDescriptorRegistry() - .getDescriptor( indicators.getPreferredSqlTypeCodeForBoolean() ); - //noinspection unchecked - return (BasicType) indicators.getTypeConfiguration() - .getBasicTypeRegistry() - .resolve( getJavaTypeDescriptor(), sqlTypeDescriptor ); - } - - //noinspection unchecked - return (BasicType) this; + public CastType getCastType() { + return CastType.YN_BOOLEAN; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ZonedDateTimeType.java b/hibernate-core/src/main/java/org/hibernate/type/ZonedDateTimeType.java index 6394a7e42a..1e960a1137 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ZonedDateTimeType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ZonedDateTimeType.java @@ -14,6 +14,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.ZonedDateTimeComparator; import org.hibernate.metamodel.model.domain.AllowableTemporalParameterType; +import org.hibernate.query.CastType; import org.hibernate.type.descriptor.java.ZonedDateTimeJavaDescriptor; import org.hibernate.type.descriptor.sql.TimestampWithTimeZoneDescriptor; import org.hibernate.type.spi.TypeConfiguration; @@ -73,4 +74,9 @@ public AllowableTemporalParameterType resolveTemporalPrecision( } } } + + @Override + public CastType getCastType() { + return CastType.ZONE_TIMESTAMP; + } } 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 0631a03b19..50d78bd41e 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 @@ -7,7 +7,9 @@ package org.hibernate.type.descriptor.sql; import java.io.Serializable; +import java.sql.Types; +import org.hibernate.query.CastType; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.java.BasicJavaDescriptor; @@ -92,4 +94,44 @@ default JdbcLiteralFormatter getJdbcLiteralFormatter(JavaTypeDescriptor ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor); + + default CastType getCastType() { + switch ( getSqlType() ) { + case Types.INTEGER: + case Types.TINYINT: + case Types.SMALLINT: + return CastType.INTEGER; + case Types.BIGINT: + return CastType.LONG; + case Types.FLOAT: + case Types.REAL: + return CastType.FLOAT; + case Types.DOUBLE: + return CastType.DOUBLE; + case Types.CHAR: + case Types.NCHAR: + case Types.VARCHAR: + case Types.NVARCHAR: + case Types.LONGVARCHAR: + case Types.LONGNVARCHAR: + return CastType.STRING; + case Types.BOOLEAN: + return CastType.BOOLEAN; + case Types.DECIMAL: + case Types.NUMERIC: + return CastType.FIXED; + case Types.DATE: + return CastType.DATE; + case Types.TIME: + return CastType.TIME; + case Types.TIMESTAMP: + return CastType.TIMESTAMP; + case Types.TIMESTAMP_WITH_TIMEZONE: + return CastType.OFFSET_TIMESTAMP; + case Types.NULL: + return CastType.NULL; + default: + return CastType.OTHER; + } + } } 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 345ab9185a..eff80b72f1 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 @@ -46,7 +46,7 @@ public String getFriendlyName() { @Override public String toString() { - return "VarbinaryTypeDescriptor)"; + return "VarbinaryTypeDescriptor"; } public int getSqlType() { diff --git a/hibernate-core/src/main/java/org/hibernate/type/internal/StandardBasicTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/type/internal/StandardBasicTypeImpl.java index 57a54a2b77..9aa08af5dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/internal/StandardBasicTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/type/internal/StandardBasicTypeImpl.java @@ -6,10 +6,14 @@ */ package org.hibernate.type.internal; +import java.sql.Types; + import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.query.CastType; import org.hibernate.type.AbstractSingleColumnStandardBasicType; import org.hibernate.type.BasicType; import org.hibernate.type.SqlTypeDescriptorIndicatorCapable; +import org.hibernate.type.descriptor.java.BooleanTypeDescriptor; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators; @@ -51,4 +55,20 @@ public BasicType resolveIndicatedType(SqlTypeDescriptorIndicators indicators) { .getBasicTypeRegistry() .resolve( getJavaTypeDescriptor(), recommendedSqlType ); } + + @Override + public CastType getCastType() { + if ( getJavaTypeDescriptor() == BooleanTypeDescriptor.INSTANCE ) { + switch ( sqlType() ) { + case Types.BIT: + case Types.SMALLINT: + case Types.TINYINT: + case Types.INTEGER: + return CastType.INTEGER_BOOLEAN; + case Types.CHAR: + return CastType.YN_BOOLEAN; + } + } + return super.getCastType(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java index 97f3601d4a..ddac229747 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/type/spi/TypeConfiguration.java @@ -285,6 +285,9 @@ public BasicValuedMapping resolveCastTargetType(String name) { //this one is very fragile ... works well for BIT or BOOLEAN columns only //works OK, I suppose, for integer columns, but not at all for char columns case "boolean": return getBasicTypeForJavaType( Boolean.class ); + case "truefalse": return StandardBasicTypes.TRUE_FALSE; + case "yesno": return StandardBasicTypes.YES_NO; + case "numericboolean": return StandardBasicTypes.NUMERIC_BOOLEAN; default: throw new HibernateException( "unrecognized cast target type: " + name ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/EmbeddableElementCollectionMemberOfTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/EmbeddableElementCollectionMemberOfTest.java index ee59dde36e..f4c7ae989b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/EmbeddableElementCollectionMemberOfTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/collectionelement/EmbeddableElementCollectionMemberOfTest.java @@ -2,6 +2,7 @@ import java.util.HashSet; import java.util.Set; +import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Embeddable; import javax.persistence.Entity; @@ -100,6 +101,7 @@ public void setStreet(String street) { this.street = street; } + @Column(name = "house_number") public int getNumber() { return number; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/columntransformer/ColumnTransformerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/columntransformer/ColumnTransformerTest.java index cf071a93b0..e7357aa5bb 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/columntransformer/ColumnTransformerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/columntransformer/ColumnTransformerTest.java @@ -6,16 +6,11 @@ */ package org.hibernate.orm.test.columntransformer; -import java.util.Collections; import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.graph.GraphParser; -import org.hibernate.graph.GraphSemantic; - import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; @@ -122,7 +117,7 @@ public void testStoredValues(SessionFactoryScope scope) { final String sqlString = // represents how each is mapped in the mappings - see their @ColumnTransformer#read "select size_in_cm / 2.54E0" - + ", radiusS / 2.54d" + + ", radiusS / 2.54E0" + ", diamet / 2.54E0" + " from t_staff" + " where t_staff.id = 4"; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceValueExtractor.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceValueExtractor.java index cb6dc3afaa..60b840af69 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceValueExtractor.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/SequenceValueExtractor.java @@ -18,9 +18,9 @@ import org.hibernate.dialect.DerbyDialect; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.HSQLDialect; -import org.hibernate.dialect.MariaDB103Dialect; -import org.hibernate.dialect.Oracle8iDialect; -import org.hibernate.dialect.SQLServer2012Dialect; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.exception.GenericJDBCException; import org.hibernate.jdbc.Work; @@ -41,10 +41,10 @@ public SequenceValueExtractor(Dialect dialect, String sequenceName) { else if ( dialect instanceof DB2Dialect ) { queryString = "values PREVIOUS value for " + sequenceName; } - else if ( dialect instanceof Oracle8iDialect ) { + else if ( dialect instanceof OracleDialect && dialect.getVersion() >= 8 ) { queryString = "select " + sequenceName + ".currval from dual"; } - else if ( dialect instanceof SQLServer2012Dialect ) { + else if ( dialect instanceof SQLServerDialect && dialect.getVersion() >= 11 ) { queryString = "SELECT CONVERT(varchar(200), Current_value) FROM sys.sequences WHERE name = '" + sequenceName + "'"; } else if ( dialect instanceof HSQLDialect ) { @@ -55,7 +55,7 @@ else if ( dialect instanceof AbstractHANADialect ) { queryString = "select " + sequenceName + ".currval from sys.dummy"; } - else if ( dialect instanceof MariaDB103Dialect ) { + else if ( dialect instanceof MariaDBDialect && dialect.getVersion() >= 1030 ) { queryString = "select LASTVAL(" + sequenceName + ")"; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyFixWithSequenceGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyFixWithSequenceGeneratorTest.java index 535ca9a96e..b226de653c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyFixWithSequenceGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyFixWithSequenceGeneratorTest.java @@ -75,8 +75,12 @@ public EntityManagerFactory produceEntityManagerFactory() { @AfterAll public void releaseResources() { - new SchemaExport().drop( EnumSet.of( TargetType.DATABASE ), metadata ); - StandardServiceRegistryBuilder.destroy( serviceRegistry ); + if ( metadata != null ) { + new SchemaExport().drop( EnumSet.of( TargetType.DATABASE ), metadata ); + } + if ( serviceRegistry != null ) { + StandardServiceRegistryBuilder.destroy( serviceRegistry ); + } } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyLogTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyLogTest.java index 927f772bc9..afd581d3f7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyLogTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyLogTest.java @@ -73,8 +73,12 @@ public EntityManagerFactory produceEntityManagerFactory() { @AfterAll public void releaseResources() { - new SchemaExport().drop( EnumSet.of( TargetType.DATABASE ), metadata ); - StandardServiceRegistryBuilder.destroy( serviceRegistry ); + if ( metadata != null ) { + new SchemaExport().drop( EnumSet.of( TargetType.DATABASE ), metadata ); + } + if ( serviceRegistry != null ) { + StandardServiceRegistryBuilder.destroy( serviceRegistry ); + } } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyWithoutSequenceGeneratorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyWithoutSequenceGeneratorTest.java index 0ee363d9fc..f50232d8f0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyWithoutSequenceGeneratorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/id/hhh12973/SequenceMismatchStrategyWithoutSequenceGeneratorTest.java @@ -74,8 +74,12 @@ public EntityManagerFactory produceEntityManagerFactory() { @AfterAll public void releaseResources() { - new SchemaExport().drop( EnumSet.of( TargetType.DATABASE ), metadata ); - StandardServiceRegistryBuilder.destroy( serviceRegistry ); + if ( metadata != null ) { + new SchemaExport().drop( EnumSet.of( TargetType.DATABASE ), metadata ); + } + if ( serviceRegistry != null ) { + StandardServiceRegistryBuilder.destroy( serviceRegistry ); + } } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/inheritance/User.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/inheritance/User.java index 41091bded4..983449eb74 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/inheritance/User.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/naturalid/inheritance/User.java @@ -6,6 +6,7 @@ */ package org.hibernate.orm.test.mapping.naturalid.inheritance; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; @@ -24,6 +25,7 @@ public User(String uid) { super( uid ); } + @Column(name = "user_level") public String getLevel() { return level; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/SortNaturalByDefaultTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/SortNaturalByDefaultTests.java index 43affc6b4f..49ad1ab388 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/SortNaturalByDefaultTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/SortNaturalByDefaultTests.java @@ -5,6 +5,7 @@ import java.util.SortedSet; import java.util.TreeSet; import javax.persistence.CascadeType; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @@ -135,6 +136,7 @@ public static class Phone implements Comparable { @GeneratedValue private Long id; + @Column(name = "phone_number") private String number; public Phone() { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/onetomany/OneToManySelfReferenceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/onetomany/OneToManySelfReferenceTest.java index 01cf4d0637..76301d2fd4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/onetomany/OneToManySelfReferenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/onetomany/OneToManySelfReferenceTest.java @@ -65,8 +65,10 @@ public void setUp(SessionFactoryScope scope) { @AfterEach public void tearDown(SessionFactoryScope scope) { scope.inTransaction( - session -> - session.createQuery( "delete from Event" ).executeUpdate() + session -> { + session.createQuery( "update Event e set e.parent = null" ).executeUpdate(); + session.createQuery( "delete from Event" ).executeUpdate(); + } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java index 757cce83ee..0e877940d9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/FunctionTests.java @@ -6,24 +6,20 @@ */ package org.hibernate.orm.test.query.hql; -import org.hibernate.boot.MetadataSources; -import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.dialect.DerbyDialect; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.junit5.SessionFactoryBasedFunctionalTest; import org.hibernate.testing.orm.domain.StandardDomainModel; import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; -import org.hibernate.testing.orm.junit.DialectFeatureCheck; import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.FailureExpected; import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import javax.persistence.EntityManager; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; @@ -43,25 +39,24 @@ @ServiceRegistry @DomainModel( standardModels = StandardDomainModel.GAMBIT ) @SessionFactory -public class FunctionTests extends SessionFactoryBasedFunctionalTest { +public class FunctionTests { - @Override - protected void applyMetadataSources(MetadataSources metadataSources) { - StandardDomainModel.GAMBIT.getDescriptor().applyDomainModel(metadataSources); - } - - @Override - protected void sessionFactoryBuilt(SessionFactoryImplementor factory) { - EntityManager em = factory.createEntityManager(); - em.getTransaction().begin(); - EntityOfBasics entity = new EntityOfBasics(); - entity.setId(12); - em.persist(entity); - em.getTransaction().commit(); - em.close(); + @BeforeAll + public void prepareData(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + EntityOfBasics entity = new EntityOfBasics(); + entity.setId(123); + entity.setTheDate( new Date( 74, 2, 25 ) ); + entity.setTheTime( new Time( 23, 10, 8 ) ); + entity.setTheTimestamp( new Timestamp( System.currentTimeMillis() ) ); + em.persist(entity); + } + ); } @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsCharCodeConversion.class) public void testAsciiChrFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -111,7 +106,7 @@ public void testConcatFunctionParameters(SessionFactoryScope scope) { public void testCoalesceFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select coalesce(null, e.gender, e.convertedGender) from EntityOfBasics e") + session.createQuery("select coalesce(nullif('',''), e.gender, e.convertedGender) from EntityOfBasics e") .list(); session.createQuery("select ifnull(e.gender, e.convertedGender) from EntityOfBasics e") .list(); @@ -159,7 +154,7 @@ public void testMathFunctions(SessionFactoryScope scope) { .list(); session.createQuery("select exp(e.theDouble), ln(e.theDouble + 1) from EntityOfBasics e") .list(); - session.createQuery("select power(e.theDouble, 2.5) from EntityOfBasics e") + session.createQuery("select power(e.theDouble + 1, 2.5) from EntityOfBasics e") .list(); session.createQuery("select ceiling(e.theDouble), floor(e.theDouble) from EntityOfBasics e") .list(); @@ -269,7 +264,7 @@ public void testOverlayFunction(SessionFactoryScope scope) { } @Test - @FailureExpected(reason = "needs indirection in positional parameter bindings") + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support a parameter in the 'length' function or the 'char' function which we render as emulation") public void testOverlayFunctionParameters(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -295,6 +290,7 @@ public void testOverlayFunctionParameters(SessionFactoryScope scope) { } @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsReplace.class) public void testReplaceFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -342,6 +338,7 @@ public void testPadFunction(SessionFactoryScope scope) { } @Test + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support a parameter in the 'length' function or the 'char' function which we render as emulation") public void testPadFunctionParameters(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -361,6 +358,10 @@ public void testPadFunctionParameters(SessionFactoryScope scope) { public void testCastFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { + assertThat( ((String) session.createQuery("select cast(e.theBoolean as String) from EntityOfBasics e").getSingleResult()).toLowerCase(), is("false") ); + assertThat( ((String) session.createQuery("select cast(e.theNumericBoolean as String) from EntityOfBasics e").getSingleResult()).toLowerCase(), is("false") ); + assertThat( ((String) session.createQuery("select cast(e.theStringBoolean as String) from EntityOfBasics e").getSingleResult()).toLowerCase(), is("false") ); + session.createQuery("select cast(e.theDate as String), cast(e.theTime as String), cast(e.theTimestamp as String) from EntityOfBasics e") .list(); session.createQuery("select cast(e.id as String), cast(e.theInt as String), cast(e.theDouble as String) from EntityOfBasics e") @@ -371,10 +372,6 @@ public void testCastFunction(SessionFactoryScope scope) { .list(); session.createQuery("select cast(e.theString as String(15)), cast(e.theDouble as String(8)) from EntityOfBasics e") .list(); - session.createQuery("select cast(e.theString as Binary) from EntityOfBasics e") - .list(); - session.createQuery("select cast(e.theString as Binary(10)) from EntityOfBasics e") - .list(); session.createQuery("select cast('1002342345234523.452435245245243' as BigDecimal) from EntityOfBasics") .list(); @@ -427,6 +424,34 @@ public void testCastFunction(SessionFactoryScope scope) { assertThat( session.createQuery("select cast(date 1911-10-09 as String)").getSingleResult(), is("1911-10-09") ); assertThat( session.createQuery("select cast(time 12:13:14 as String)").getSingleResult(), is("12:13:14") ); assertThat( (String) session.createQuery("select cast(datetime 1911-10-09 12:13:14 as String)").getSingleResult(), startsWith("1911-10-09 12:13:14") ); + + assertThat( session.createQuery("select cast(1 as NumericBoolean)").getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(0 as NumericBoolean)").getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast(true as YesNo)").getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(false as YesNo)").getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast(1 as YesNo)").getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(0 as YesNo)").getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast(true as TrueFalse)").getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(false as TrueFalse)").getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast(1 as TrueFalse)").getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(0 as TrueFalse)").getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast('Y' as YesNo)").getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast('N' as YesNo)").getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast('T' as TrueFalse)").getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast('F' as TrueFalse)").getSingleResult(), is(false) ); + } + ); + } + + @Test + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support casting to the binary types") + public void testCastFunctionBinary(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery("select cast(e.theString as Binary) from EntityOfBasics e") + .list(); + session.createQuery("select cast(e.theString as Binary(10)) from EntityOfBasics e") + .list(); } ); } @@ -515,7 +540,9 @@ public void testAggregateFunctions(SessionFactoryScope scope) { session -> { session.createQuery("select avg(e.theDouble), avg(abs(e.theDouble)), min(e.theDouble), max(e.theDouble), sum(e.theDouble), sum(e.theInt) from EntityOfBasics e") .list(); - session.createQuery("select avg(distinct e.theInt), sum(distinct e.theInt) from EntityOfBasics e") + session.createQuery("select avg(distinct e.theInt) from EntityOfBasics e") + .list(); + session.createQuery("select sum(distinct e.theInt) from EntityOfBasics e") .list(); session.createQuery("select any(e.theInt > 0), every(e.theInt > 0) from EntityOfBasics e") .list(); @@ -763,11 +790,7 @@ public void testExtractFunction(SessionFactoryScope scope) { .list(); session.createQuery("select extract(day of month from e.theDate) from EntityOfBasics e") .list(); - session.createQuery("select extract(day of week from e.theDate) from EntityOfBasics e") - .list(); - session.createQuery("select extract(week from e.theDate) from EntityOfBasics e") - .list(); session.createQuery("select extract(quarter from e.theDate) from EntityOfBasics e") .list(); @@ -813,6 +836,20 @@ public void testExtractFunction(SessionFactoryScope scope) { ); } + @Test + public void testExtractFunctionWeek(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery("select extract(day of week from e.theDate) from EntityOfBasics e") + .list(); + + session.createQuery("select extract(week from e.theDate) from EntityOfBasics e") + .list(); + + } + ); + } + @Test @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsTimezoneTypes.class) public void testExtractFunctionTimeZone(SessionFactoryScope scope) { @@ -833,10 +870,6 @@ public void testExtractFunctionTimeZone(SessionFactoryScope scope) { 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) @@ -932,23 +965,15 @@ public void testExtractFunctionWithAssertions(SessionFactoryScope scope) { session.createQuery("select extract(time from local datetime) from EntityOfBasics").getResultList().get(0), instanceOf(LocalTime.class) ); - session.delete(entity); } ); } @Test + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support formatting temporal types to strings") public void testFormat(SessionFactoryScope scope) { scope.inTransaction( session -> { - EntityOfBasics entity = new EntityOfBasics(); - entity.setId(123); - 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") @@ -969,8 +994,8 @@ public void testFormat(SessionFactoryScope scope) { } @Test - public void testGrouping() { - inTransaction( + public void testGrouping(SessionFactoryScope scope) { + scope.inTransaction( session -> { session.createQuery("select max(e.theDouble), e.gender, e.theInt from EntityOfBasics e group by e.gender, e.theInt") .list(); @@ -980,12 +1005,21 @@ public void testGrouping() { @Test @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGroupByRollup.class) - public void testGroupingFunctions() { - inTransaction( + public void testGroupByRollup(SessionFactoryScope scope) { + scope.inTransaction( session -> { session.createQuery("select avg(e.theDouble), e.gender, e.theInt from EntityOfBasics e group by rollup(e.gender, e.theInt)") .list(); - session.createQuery("select sum(e.theDouble), e.gender, e.theInt from EntityOfBasics e group by cube(e.gender, e.theInt)") + } + ); + } + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGroupByGroupingSets.class) + public void testGroupByGroupingSets(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery("select avg(e.theDouble), e.gender, e.theInt from EntityOfBasics e group by cube(e.gender, e.theInt)") .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 bda0bab3c3..cdadf45aee 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 @@ -12,6 +12,8 @@ import java.time.LocalDate; import java.time.LocalTime; +import org.hibernate.dialect.DerbyDialect; + import org.hibernate.testing.orm.domain.StandardDomainModel; import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; import org.hibernate.testing.orm.junit.DialectFeatureChecks; @@ -20,6 +22,8 @@ import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.instanceOf; @@ -34,6 +38,21 @@ @DomainModel( standardModels = StandardDomainModel.GAMBIT ) @SessionFactory public class StandardFunctionTests { + + @BeforeAll + public void prepareData(SessionFactoryScope scope) { + scope.inTransaction( + em -> { + EntityOfBasics entity = new EntityOfBasics(); + entity.setId(123); + entity.setTheDate( new Date( 74, 2, 25 ) ); + entity.setTheTime( new Time( 23, 10, 8 ) ); + entity.setTheTimestamp( new Timestamp( System.currentTimeMillis() ) ); + em.persist(entity); + } + ); + } + @Test public void currentTimestampTests(SessionFactoryScope scope) { scope.inTransaction( @@ -105,11 +124,11 @@ public void localDateTests(SessionFactoryScope scope) { session.createQuery( "select local_date from EntityOfBasics" ).list(); session.createQuery( "select local_date() from EntityOfBasics" ).list(); - session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_date" ).list(); - session.createQuery( "select e from EntityOfBasics e where e.theTimestamp = local_date()" ).list(); + session.createQuery( "select e from EntityOfBasics e where e.theDate = local_date" ).list(); + session.createQuery( "select e from EntityOfBasics e where e.theDate = local_date()" ).list(); - session.createQuery( "select e from EntityOfBasics e where local_date between e.theTimestamp and e.theTimestamp" ).list(); - session.createQuery( "select e from EntityOfBasics e where local_date() between e.theTimestamp and e.theTimestamp" ).list(); + session.createQuery( "select e from EntityOfBasics e where local_date between e.theDate and e.theDate" ).list(); + session.createQuery( "select e from EntityOfBasics e where local_date() between e.theDate and e.theDate" ).list(); assertThat( session.createQuery( "select local_date" ).getSingleResult(), @@ -154,7 +173,7 @@ public void testConcatFunction(SessionFactoryScope scope) { public void testCoalesceFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select coalesce(null, e.gender, e.convertedGender) from EntityOfBasics e") + session.createQuery("select coalesce(nullif('',''), e.gender, e.convertedGender) from EntityOfBasics e") .list(); session.createQuery("select ifnull(e.gender, e.convertedGender) from EntityOfBasics e") .list(); @@ -191,9 +210,9 @@ public void testMathFunctions(SessionFactoryScope scope) { .list(); session.createQuery("select abs(e.theDouble), sign(e.theDouble), sqrt(e.theDouble) from EntityOfBasics e") .list(); - session.createQuery("select exp(e.theDouble), ln(e.theDouble) from EntityOfBasics e") + session.createQuery("select exp(e.theDouble), ln(e.theDouble + 1) from EntityOfBasics e") .list(); - session.createQuery("select power(e.theDouble, 2.5) from EntityOfBasics e") + session.createQuery("select power(e.theDouble + 1, 2.5) from EntityOfBasics e") .list(); session.createQuery("select ceiling(e.theDouble), floor(e.theDouble) from EntityOfBasics e") .list(); @@ -232,6 +251,7 @@ public void testTimestampAddDiffFunctions(SessionFactoryScope scope) { } @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsCharCodeConversion.class) public void testAsciiChrFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -311,6 +331,7 @@ public void testLocateFunction(SessionFactoryScope scope) { } @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsReplace.class) public void testReplaceFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -612,14 +633,14 @@ public void testExtractFunction(SessionFactoryScope scope) { public void testExtractFunctionTimeZone(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select extract(offset hour from e.theTime) from EntityOfBasics e") + session.createQuery("select extract(offset hour from e.theZonedDateTime) from EntityOfBasics e") .list(); // the grammar rule is defined as `HOUR | MINUTE` so no idea how both ever worked. -// session.createQuery("select extract(offset hour minute from e.theTime) from EntityOfBasics e") +// session.createQuery("select extract(offset hour minute from e.theZonedDateTime) from EntityOfBasics e") // .list(); - session.createQuery("select extract(offset from e.theTimestamp) from EntityOfBasics e") + session.createQuery("select extract(offset from e.theZonedDateTime) from EntityOfBasics e") .list(); } ); @@ -640,164 +661,147 @@ public void isolated(SessionFactoryScope scope) { public void testExtractFunctionWithAssertions(SessionFactoryScope scope) { scope.inTransaction( session -> { - EntityOfBasics entity = new EntityOfBasics(); - entity.setId(1); - session.save(entity); + assertThat( + session.createQuery( + "select extract(week of year from {2019-01-01}) from EntityOfBasics b where b.id = 123" ) + .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 ) + ); } ); - - 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 @@ -874,60 +878,43 @@ public void testAggregateFunctions(SessionFactoryScope scope) { session -> { session.createQuery("select avg(e.theDouble), avg(abs(e.theDouble)), min(e.theDouble), max(e.theDouble), sum(e.theDouble), sum(e.theInt) from EntityOfBasics e") .list(); - session.createQuery("select avg(distinct e.theInt), sum(distinct e.theInt) from EntityOfBasics e") + session.createQuery("select sum(distinct e.theInt) from EntityOfBasics e") + .list(); + session.createQuery("select sum(distinct e.theInt) from EntityOfBasics e") .list(); } ); } @Test + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support formatting temporal types to strings") public void testFormat(SessionFactoryScope scope) { scope.inTransaction( session -> { - EntityOfBasics entity = new EntityOfBasics(); - entity.setId(123); - entity.setTheDate( new Date( 74, 2, 25 ) ); - entity.setTheTime( new Time( 23, 10, 8 ) ); - entity.setTheTimestamp( new Timestamp( System.currentTimeMillis() ) ); - session.persist( entity ); + 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" ) + ); } ); - - 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-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryParameterTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryParameterTests.java index d6e75f4feb..52ce5b5c9f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryParameterTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryParameterTests.java @@ -35,7 +35,7 @@ public class NativeQueryParameterTests { public void testBasicParameterBinding(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createNativeQuery( "select t.id, t.ticket_key, t.subject from ticket t where t.ticket_key = ?" ) + session.createNativeQuery( "select t.id, t.ticket_key, t.subject from Ticket t where t.ticket_key = ?" ) .setParameter( 1, "ABC-123" ) .list(); } @@ -45,7 +45,7 @@ public void testBasicParameterBinding(SessionFactoryScope scope) { @Test public void testJpaStylePositionalParametersInNativeSql(SessionFactoryScope scope) { scope.inTransaction( - s -> s.createNativeQuery( "select t.subject from ticket t where t.ticket_key = ?1" ).setParameter( 1, "ABC-123" ).list() + s -> s.createNativeQuery( "select t.subject from Ticket t where t.ticket_key = ?1" ).setParameter( 1, "ABC-123" ).list() ); } @@ -53,7 +53,7 @@ public void testJpaStylePositionalParametersInNativeSql(SessionFactoryScope scop public void testTypedParameterBinding(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createNativeQuery( "select t.id, t.ticket_key, t.subject from ticket t where t.ticket_key = ?" ) + session.createNativeQuery( "select t.id, t.ticket_key, t.subject from Ticket t where t.ticket_key = ?" ) .setParameter( 1, "ABC-123", StandardBasicTypes.STRING ) .list(); } @@ -63,7 +63,7 @@ public void testTypedParameterBinding(SessionFactoryScope scope) { @Test public void testTemporalParameterBinding(SessionFactoryScope scope) { final String qryString = "select i.id, i.effectiveStart, i.effectiveEnd " + - " from incident i" + + " from Incident i" + " where i.reported BETWEEN ? AND ?"; scope.inTransaction( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java index 04548cbe75..382726ec02 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderTests.java @@ -14,6 +14,7 @@ import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.SQLServerDialect; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.internal.BasicValuedSingularAttributeMapping; @@ -80,10 +81,11 @@ public void fullyImplicitTest(SessionFactoryScope scope) { public void fullyImplicitTest2(SessionFactoryScope scope) { scope.inTransaction( session -> { - // DB2 and Derby return an Integer for count by default + // DB2, Derby and SQL Server return an Integer for count by default Assumptions.assumeThat( session.getJdbcServices().getDialect() ) .isNotInstanceOf( DB2Dialect.class ) - .isNotInstanceOf( DerbyDialect.class ); + .isNotInstanceOf( DerbyDialect.class ) + .isNotInstanceOf( SQLServerDialect.class ); final String sql = "select count(theString) from EntityOfBasics"; final NativeQueryImplementor query = session.createNativeQuery( sql ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/set/SetOperationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/set/SetOperationTest.java index 6e2ef5e841..ed39dcbb97 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/set/SetOperationTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/set/SetOperationTest.java @@ -75,6 +75,23 @@ public void testUnionAll(SessionFactoryScope scope) { @Test @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsUnion.class) public void testUnionAllLimit(SessionFactoryScope scope) { + scope.inSession( + session -> { + List list = session.createQuery( + "(select e.id, e from EntityOfLists e where e.id = 1 " + + "union all " + + "select e.id, e from EntityOfLists e where e.id = 2) " + + "order by 1 fetch first 1 row only", + Tuple.class + ).list(); + assertThat( list.size(), is( 1 ) ); + } + ); + } + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsUnion.class) + public void testUnionAllLimitSubquery(SessionFactoryScope scope) { scope.inSession( session -> { List list = session.createQuery( @@ -84,6 +101,23 @@ public void testUnionAllLimit(SessionFactoryScope scope) { "order by 1 fetch first 1 row only", Tuple.class ).list(); + assertThat( list.size(), is( 2 ) ); + } + ); + } + + @Test + @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsUnion.class) + public void testUnionAllLimitNested(SessionFactoryScope scope) { + scope.inSession( + session -> { + List list = session.createQuery( + "(select e.id, e from EntityOfLists e where e.id = 1 " + + "union all " + + "(select e.id, e from EntityOfLists e where e.id = 2 order by 1 fetch first 1 row only)) " + + "order by 1 fetch first 1 row only", + Tuple.class + ).list(); assertThat( list.size(), is( 1 ) ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java index 54deb1bd89..3e87930ec8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/ast/SmokeTests.java @@ -120,9 +120,13 @@ public void testSimpleHqlInterpretation(SessionFactoryScope scope) { sqlAst ).translate( null, QueryOptions.NONE ); + final String separator = session.getSessionFactory() + .getJdbcServices() + .getDialect() + .getTableAliasSeparator(); assertThat( jdbcSelectOperation.getSql(), - is( "select s1_0.name from mapping_simple_entity as s1_0" ) + is( "select s1_0.name from mapping_simple_entity" + separator + "s1_0" ) ); } ); @@ -216,9 +220,13 @@ public void testConvertedHqlInterpretation(SessionFactoryScope scope) { sqlAst ).translate( null, QueryOptions.NONE ); + final String separator = session.getSessionFactory() + .getJdbcServices() + .getDialect() + .getTableAliasSeparator(); assertThat( jdbcSelectOperation.getSql(), - is( "select s1_0.gender from mapping_simple_entity as s1_0" ) + is( "select s1_0.gender from mapping_simple_entity" + separator + "s1_0" ) ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/EntityWithLazyManyToOneSelfReferenceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/EntityWithLazyManyToOneSelfReferenceTest.java index dd4bfdaad5..047321f2ab 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/EntityWithLazyManyToOneSelfReferenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/EntityWithLazyManyToOneSelfReferenceTest.java @@ -67,6 +67,7 @@ public void setUp(SessionFactoryScope scope) { public void tearDown(SessionFactoryScope scope) { scope.inTransaction( session -> { + session.createQuery( "update EntityWithLazyManyToOneSelfReference e set e.other = null" ).executeUpdate(); session.createQuery( "delete from EntityWithLazyManyToOneSelfReference" ).executeUpdate(); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/EntityWithManyToOneSelfReferenceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/EntityWithManyToOneSelfReferenceTest.java index cb47fd05bd..0fca7978a0 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/EntityWithManyToOneSelfReferenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/manytoone/EntityWithManyToOneSelfReferenceTest.java @@ -66,6 +66,7 @@ public void setUp(SessionFactoryScope scope) { public void tearDown(SessionFactoryScope scope) { scope.inTransaction( session -> { + session.createQuery( "update EntityWithManyToOneSelfReference e set e.other = null" ).executeUpdate(); session.createQuery( "delete from EntityWithManyToOneSelfReference" ).executeUpdate(); } ); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectContext.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectContext.java new file mode 100644 index 0000000000..238f257534 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectContext.java @@ -0,0 +1,59 @@ +/* + * 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.testing.junit5; + +import java.lang.reflect.Constructor; +import java.sql.Connection; +import java.sql.Driver; +import java.util.Properties; + +import org.hibernate.HibernateException; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter; +import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.internal.util.ReflectHelper; + +/** + * @author Christian Beikov + */ +public final class DialectContext { + + private static final Dialect dialect; + + static { + final Properties properties = Environment.getProperties(); + final String dialectName = properties.getProperty( Environment.DIALECT ); + if ( dialectName == null ) { + throw new HibernateException( "The dialect was not set. Set the property hibernate.dialect." ); + } + try { + final Class dialectClass = ReflectHelper.classForName( dialectName ); + final Constructor constructor = dialectClass.getConstructor( DialectResolutionInfo.class ); + Driver driver = (Driver) Class.forName( properties.getProperty( Environment.DRIVER ) ).newInstance(); + Properties props = new Properties(); + props.setProperty( "user", properties.getProperty( Environment.USER ) ); + props.setProperty( "password", properties.getProperty( Environment.PASS ) ); + try (Connection connection = driver.connect( properties.getProperty( Environment.URL ), props )) { + dialect = constructor.newInstance( new DatabaseMetaDataDialectResolutionInfoAdapter( connection.getMetaData() ) ); + } + } + catch (ClassNotFoundException cnfe) { + throw new HibernateException( "Dialect class not found: " + dialectName ); + } + catch (Exception e) { + throw new HibernateException( "Could not instantiate given dialect class: " + dialectName, e ); + } + } + + private DialectContext() { + } + + public static Dialect getDialect() { + return dialect; + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectFilterExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectFilterExtension.java index bfede5e38a..f66b875e2e 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectFilterExtension.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/DialectFilterExtension.java @@ -37,16 +37,6 @@ public class DialectFilterExtension implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - if ( !context.getTestInstance().isPresent() ) { - assert !context.getTestMethod().isPresent(); - - return ConditionEvaluationResult.enabled( - "No test-instance was present - " + - "likely that test was not defined with a per-class test lifecycle; " + - "skipping Dialect checks for this context [" + context.getDisplayName() + "]" - ); - } - final Dialect dialect = getDialect( context ); if ( dialect == null ) { throw new RuntimeException( "#getDialect returned null" ); @@ -135,7 +125,7 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con try { final DialectFeatureCheck dialectFeatureCheck = effectiveRequiresDialectFeature.feature() .newInstance(); - final boolean applicable = dialectFeatureCheck.apply( getDialect( context ) ); + final boolean applicable = dialectFeatureCheck.apply( dialect ); final boolean reverse = effectiveRequiresDialectFeature.reverse(); if ( !( applicable ^ reverse ) ) { return ConditionEvaluationResult.disabled( @@ -156,22 +146,6 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con } private Dialect getDialect(ExtensionContext context) { - final Optional sfScope = SessionFactoryScopeExtension.findSessionFactoryScope( context ); - if ( !sfScope.isPresent() ) { - final Optional emScope = EntityManagerFactoryScopeExtension.findEntityManagerFactoryScope( context ); - if ( !emScope.isPresent() ) { - final Optional dialectAccess = Optional.ofNullable( - (DialectAccess) context.getStore( DialectAccess.NAMESPACE ) - .get( context.getRequiredTestInstance() ) ); - if ( !dialectAccess.isPresent() ) { - throw new RuntimeException( - "Could not locate any DialectAccess implementation in JUnit ExtensionContext" ); - } - return dialectAccess.get().getDialect(); - } - return emScope.get().getDialect(); - } - - return sfScope.get().getDialect(); + return DialectContext.getDialect(); } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryAccess.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryAccess.java index b0b7e99054..648a56917c 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryAccess.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/EntityManagerFactoryAccess.java @@ -9,7 +9,6 @@ import javax.persistence.EntityManagerFactory; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.spi.SessionFactoryImplementor; /** * Contract for things that expose an EntityManagerFactory @@ -21,6 +20,6 @@ public interface EntityManagerFactoryAccess extends DialectAccess { @Override default Dialect getDialect() { - return Dialect.getDialect(); + return DialectContext.getDialect(); } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryAccess.java b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryAccess.java index 1fb7c7435f..775406d9c1 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryAccess.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/junit5/SessionFactoryAccess.java @@ -19,6 +19,6 @@ public interface SessionFactoryAccess extends DialectAccess { @Override default Dialect getDialect() { - return getSessionFactory().getJdbcServices().getDialect(); + return DialectContext.getDialect(); } } 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 f5a64fe388..7a58f22dd3 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 @@ -29,6 +29,7 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; +import org.hibernate.annotations.SqlType; import org.hibernate.annotations.SqlTypeCode; /** @@ -49,7 +50,9 @@ public enum Gender { } private Integer id; - private Boolean theBoolean; + private Boolean theBoolean = false; + private Boolean theNumericBoolean = false; + private Boolean theStringBoolean = false; private String theString; private Integer theInteger; private int theInt; @@ -258,6 +261,24 @@ public void setTheBoolean(Boolean theBoolean) { this.theBoolean = theBoolean; } + @SqlTypeCode( Types.INTEGER ) + public Boolean isTheNumericBoolean() { + return theNumericBoolean; + } + + public void setTheNumericBoolean(Boolean theNumericBoolean) { + this.theNumericBoolean = theNumericBoolean; + } + + @SqlTypeCode( Types.CHAR ) + public Boolean isTheStringBoolean() { + return theStringBoolean; + } + + public void setTheStringBoolean(Boolean theStringBoolean) { + this.theStringBoolean = theStringBoolean; + } + @Convert( converter = MutableValueConverter.class ) public MutableValue getMutableValue() { return mutableValue; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java index 660a1be642..7ff8e489e0 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/domain/gambit/Shirt.java @@ -7,6 +7,7 @@ package org.hibernate.testing.orm.domain.gambit; import javax.persistence.AttributeConverter; +import javax.persistence.Column; import javax.persistence.Convert; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -25,6 +26,7 @@ public class Shirt { private String data; @Enumerated + @Column(name = "shirt_size") private Size size; @Enumerated(EnumType.STRING) diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java index b6e2558f92..2f30c5e17f 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFeatureChecks.java @@ -16,6 +16,7 @@ import org.hibernate.dialect.GroupBySummarizationRenderingStrategy; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.HSQLDialect; +import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MaxDBDialect; import org.hibernate.dialect.MimerSQLDialect; import org.hibernate.dialect.MySQLDialect; @@ -104,7 +105,7 @@ public boolean apply(Dialect dialect) { } } - public static class SupportSubqueryAsLeftHandSideInPredicate implements DialectFeatureCheck { + public static class SupportsSubqueryAsLeftHandSideInPredicate implements DialectFeatureCheck { public boolean apply(Dialect dialect) { return dialect.supportsSubselectAsInPredicateLHS(); } @@ -233,7 +234,7 @@ public boolean apply(Dialect dialect) { public static class SupportsPadWithChar implements DialectFeatureCheck { public boolean apply(Dialect dialect) { - return !(dialect instanceof DerbyDialect ); + return !( dialect instanceof DerbyDialect ); } } @@ -243,6 +244,17 @@ public boolean apply(Dialect dialect) { } } + public static class SupportsGroupByGroupingSets implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + return dialect.getGroupBySummarizationRenderingStrategy() != GroupBySummarizationRenderingStrategy.NONE + && !( dialect instanceof DerbyDialect ) + // MariaDB only supports ROLLUP + && !( dialect instanceof MariaDBDialect ) + // MySQL only supports ROLLUP + && !( dialect instanceof MySQLDialect ); + } + } + public static class SupportsTimezoneTypes implements DialectFeatureCheck { public boolean apply(Dialect dialect) { return dialect.supportsTimezoneTypes(); @@ -277,6 +289,7 @@ public boolean apply(Dialect dialect) { || dialect instanceof DB2Dialect || dialect instanceof FirebirdDialect && dialect.getVersion() >= 300 || dialect instanceof H2Dialect && dialect.getVersion() >= 104198 + || dialect instanceof MariaDBDialect && dialect.getVersion() >= 1020 || dialect instanceof MySQLDialect && dialect.getVersion() >= 802 || dialect instanceof OracleDialect || dialect instanceof PostgreSQLDialect @@ -291,4 +304,18 @@ public boolean apply(Dialect dialect) { } } + public static class SupportsCharCodeConversion implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + // Derby doesn't support the `ASCII` or `CHR` functions + return !( dialect instanceof DerbyDialect ); + } + } + + public static class SupportsReplace implements DialectFeatureCheck { + public boolean apply(Dialect dialect) { + // Derby doesn't support the `REPLACE` function + return !( dialect instanceof DerbyDialect ); + } + } + } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java index 324bddc167..c359a526cd 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DialectFilterExtension.java @@ -9,10 +9,9 @@ import java.util.List; import java.util.Locale; -import org.hibernate.boot.registry.StandardServiceRegistry; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.testing.junit5.DialectContext; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; @@ -30,16 +29,6 @@ public class DialectFilterExtension implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { - if ( !context.getTestInstance().isPresent() ) { - assert !context.getTestMethod().isPresent(); - - return ConditionEvaluationResult.enabled( - "No test-instance was present - " + - "likely that test was not defined with a per-class test lifecycle; " + - "skipping Dialect checks for this context [" + context.getDisplayName() + "]" - ); - } - final Dialect dialect = getDialect( context ); if ( dialect == null ) { throw new RuntimeException( "#getDialect returned null" ); @@ -109,7 +98,7 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con try { final DialectFeatureCheck dialectFeatureCheck = effectiveRequiresDialectFeature.feature() .newInstance(); - if ( !dialectFeatureCheck.apply( getDialect( context ) ) ) { + if ( !dialectFeatureCheck.apply( dialect ) ) { return ConditionEvaluationResult.disabled( String.format( Locale.ROOT, @@ -127,11 +116,6 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con } private Dialect getDialect(ExtensionContext context) { - final StandardServiceRegistry serviceRegistry = ServiceRegistryExtension.findServiceRegistry( - context.getRequiredTestInstance(), - context - ); - - return serviceRegistry.getService( JdbcServices.class ).getJdbcEnvironment().getDialect(); + return DialectContext.getDialect(); } }