From e5d59b64fdb99da61fcb8ebf57c67640cc845980 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 12 Jun 2023 10:20:30 +0200 Subject: [PATCH] HHH-16773 Introduce support for group/order by PK functional dependency --- .../dialect/CockroachLegacyDialect.java | 49 ++- .../community/dialect/H2LegacyDialect.java | 14 +- .../community/dialect/HSQLLegacyDialect.java | 11 +- .../dialect/MariaDBLegacyDialect.java | 13 +- .../community/dialect/MySQLLegacyDialect.java | 16 +- .../dialect/PostgreSQLLegacyDialect.java | 20 +- .../hibernate/dialect/CockroachDialect.java | 5 + .../java/org/hibernate/dialect/Dialect.java | 8 + .../FunctionalDependencyAnalysisSupport.java | 30 ++ ...nctionalDependencyAnalysisSupportImpl.java | 78 +++++ .../java/org/hibernate/dialect/H2Dialect.java | 9 +- .../org/hibernate/dialect/HSQLDialect.java | 5 + .../org/hibernate/dialect/MariaDBDialect.java | 5 + .../org/hibernate/dialect/MySQLDialect.java | 5 + .../hibernate/dialect/PostgreSQLDialect.java | 35 +- .../EntityValuedPathInterpretation.java | 65 ++-- .../InheritanceQueryGroupByTest.java | 303 ++++++++++++++---- 17 files changed, 523 insertions(+), 148 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/FunctionalDependencyAnalysisSupport.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/FunctionalDependencyAnalysisSupportImpl.java diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java index 1b1c9731aa..c7f8715ba9 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacyDialect.java @@ -19,26 +19,13 @@ import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; -import jakarta.persistence.TemporalType; - import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.PessimisticLockException; import org.hibernate.QueryTimeoutException; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; -import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.NationalizationSupport; -import org.hibernate.dialect.PgJdbcHelper; -import org.hibernate.dialect.PostgreSQLCastingInetJdbcType; -import org.hibernate.dialect.PostgreSQLCastingIntervalSecondJdbcType; -import org.hibernate.dialect.PostgreSQLCastingJsonJdbcType; -import org.hibernate.dialect.PostgreSQLDriverKind; -import org.hibernate.dialect.RowLockStrategy; -import org.hibernate.dialect.SimpleDatabaseVersion; -import org.hibernate.dialect.SpannerDialect; -import org.hibernate.dialect.TimeZoneSupport; +import org.hibernate.dialect.*; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.function.FormatFunction; import org.hibernate.dialect.function.PostgreSQLTruncFunction; @@ -87,11 +74,38 @@ import org.hibernate.type.spi.TypeConfiguration; import org.jboss.logging.Logger; +import jakarta.persistence.TemporalType; + import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; import static org.hibernate.query.sqm.TemporalUnit.DAY; import static org.hibernate.query.sqm.TemporalUnit.EPOCH; import static org.hibernate.query.sqm.TemporalUnit.NATIVE; -import static org.hibernate.type.SqlTypes.*; +import static org.hibernate.type.SqlTypes.ARRAY; +import static org.hibernate.type.SqlTypes.BINARY; +import static org.hibernate.type.SqlTypes.BLOB; +import static org.hibernate.type.SqlTypes.CHAR; +import static org.hibernate.type.SqlTypes.CLOB; +import static org.hibernate.type.SqlTypes.GEOGRAPHY; +import static org.hibernate.type.SqlTypes.GEOMETRY; +import static org.hibernate.type.SqlTypes.INET; +import static org.hibernate.type.SqlTypes.INTEGER; +import static org.hibernate.type.SqlTypes.JSON; +import static org.hibernate.type.SqlTypes.LONG32NVARCHAR; +import static org.hibernate.type.SqlTypes.LONG32VARBINARY; +import static org.hibernate.type.SqlTypes.LONG32VARCHAR; +import static org.hibernate.type.SqlTypes.NCHAR; +import static org.hibernate.type.SqlTypes.NCLOB; +import static org.hibernate.type.SqlTypes.NVARCHAR; +import static org.hibernate.type.SqlTypes.OTHER; +import static org.hibernate.type.SqlTypes.TIME; +import static org.hibernate.type.SqlTypes.TIMESTAMP; +import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; +import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; +import static org.hibernate.type.SqlTypes.TIME_UTC; +import static org.hibernate.type.SqlTypes.TINYINT; +import static org.hibernate.type.SqlTypes.UUID; +import static org.hibernate.type.SqlTypes.VARBINARY; +import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime; @@ -1022,6 +1036,11 @@ public class CockroachLegacyDialect extends Dialect { return false; } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_GROUP_AND_CONSTANTS; + } + @Override public RowLockStrategy getWriteRowLockStrategy() { return getVersion().isSameOrAfter( 20, 1 ) ? RowLockStrategy.TABLE : RowLockStrategy.NONE; diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java index f51e2833ce..499b61ac35 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/H2LegacyDialect.java @@ -19,14 +19,7 @@ import java.util.TimeZone; import org.hibernate.PessimisticLockException; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; -import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.H2DurationIntervalSecondJdbcType; -import org.hibernate.dialect.OracleDialect; -import org.hibernate.dialect.Replacer; -import org.hibernate.dialect.SelectItemReferenceStrategy; -import org.hibernate.dialect.SimpleDatabaseVersion; -import org.hibernate.dialect.TimeZoneSupport; +import org.hibernate.dialect.*; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.hint.IndexQueryHintHandler; import org.hibernate.dialect.identity.H2FinalTableIdentityColumnSupport; @@ -829,6 +822,11 @@ public class H2LegacyDialect extends Dialect { return getVersion().isSameOrAfter( 1, 4, 198 ); } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_GROUP_AND_CONSTANTS; + } + @Override public IdentityColumnSupport getIdentityColumnSupport() { return getVersion().isSameOrAfter( 2 ) ? H2FinalTableIdentityColumnSupport.INSTANCE : H2IdentityColumnSupport.INSTANCE; diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java index 055797ab33..b88bc490ed 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java @@ -14,11 +14,7 @@ import org.hibernate.JDBCException; import org.hibernate.LockMode; import org.hibernate.StaleObjectStateException; import org.hibernate.boot.model.FunctionContributions; -import org.hibernate.dialect.BooleanDecoder; -import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.OracleDialect; -import org.hibernate.dialect.SimpleDatabaseVersion; +import org.hibernate.dialect.*; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.identity.HSQLIdentityColumnSupport; import org.hibernate.dialect.identity.IdentityColumnSupport; @@ -786,6 +782,11 @@ public class HSQLLegacyDialect extends Dialect { return false; } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_REFERENCE; + } + // Do not drop constraints explicitly, just do this by cascading instead. @Override public boolean dropConstraints() { diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacyDialect.java index ac2551f316..91f01a01de 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MariaDBLegacyDialect.java @@ -11,13 +11,7 @@ import java.sql.SQLException; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; -import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.InnoDBStorageEngine; -import org.hibernate.dialect.MySQLServerConfiguration; -import org.hibernate.dialect.MySQLStorageEngine; -import org.hibernate.dialect.NationalizationSupport; -import org.hibernate.dialect.VarcharUUIDJdbcType; +import org.hibernate.dialect.*; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.sequence.MariaDBSequenceSupport; import org.hibernate.dialect.sequence.SequenceSupport; @@ -249,6 +243,11 @@ public class MariaDBLegacyDialect extends MySQLLegacyDialect { return false; } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_GROUP_AND_CONSTANTS; + } + @Override public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData) throws SQLException { diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java index 01a1a7235f..38b16f3cc1 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/MySQLLegacyDialect.java @@ -17,16 +17,7 @@ import org.hibernate.PessimisticLockException; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; import org.hibernate.cfg.Environment; -import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.InnoDBStorageEngine; -import org.hibernate.dialect.MyISAMStorageEngine; -import org.hibernate.dialect.MySQLCastingJsonJdbcType; -import org.hibernate.dialect.MySQLServerConfiguration; -import org.hibernate.dialect.MySQLStorageEngine; -import org.hibernate.dialect.Replacer; -import org.hibernate.dialect.RowLockStrategy; -import org.hibernate.dialect.SelectItemReferenceStrategy; +import org.hibernate.dialect.*; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.dialect.hint.IndexQueryHintHandler; import org.hibernate.dialect.identity.IdentityColumnSupport; @@ -1367,6 +1358,11 @@ public class MySQLLegacyDialect extends Dialect { return getMySQLVersion().isSameOrAfter( 8 ); } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_GROUP; + } + @Override public boolean canDisableConstraints() { return true; diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java index 6995bbd15c..1d5769f784 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/PostgreSQLLegacyDialect.java @@ -26,20 +26,7 @@ import org.hibernate.QueryTimeoutException; import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; import org.hibernate.community.dialect.sequence.PostgreSQLLegacySequenceSupport; -import org.hibernate.dialect.DatabaseVersion; -import org.hibernate.dialect.Dialect; -import org.hibernate.dialect.NationalizationSupport; -import org.hibernate.dialect.OracleDialect; -import org.hibernate.dialect.PgJdbcHelper; -import org.hibernate.dialect.PostgreSQLCastingInetJdbcType; -import org.hibernate.dialect.PostgreSQLCastingIntervalSecondJdbcType; -import org.hibernate.dialect.PostgreSQLCastingJsonJdbcType; -import org.hibernate.dialect.PostgreSQLDriverKind; -import org.hibernate.dialect.PostgreSQLStructCastingJdbcType; -import org.hibernate.dialect.Replacer; -import org.hibernate.dialect.RowLockStrategy; -import org.hibernate.dialect.SelectItemReferenceStrategy; -import org.hibernate.dialect.TimeZoneSupport; +import org.hibernate.dialect.*; import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.dialect.aggregate.PostgreSQLAggregateSupport; import org.hibernate.dialect.function.CommonFunctionFactory; @@ -1287,6 +1274,11 @@ public class PostgreSQLLegacyDialect extends Dialect { return false; } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_REFERENCE; + } + @Override public RowLockStrategy getWriteRowLockStrategy() { return RowLockStrategy.TABLE; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java index f6c3218a71..bc4d5229bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -988,6 +988,11 @@ public class CockroachDialect extends Dialect { return false; } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_GROUP_AND_CONSTANTS; + } + @Override public RowLockStrategy getWriteRowLockStrategy() { return RowLockStrategy.TABLE; 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 9e1f7fc3da..89d97a5419 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -5299,4 +5299,12 @@ public abstract class Dialect implements ConversionContext, TypeContributor, Fun public DmlTargetColumnQualifierSupport getDmlTargetColumnQualifierSupport() { return DmlTargetColumnQualifierSupport.NONE; } + + /** + * Get this dialect's level of support for primary key functional dependency analysis + * within {@code GROUP BY} and {@code ORDER BY} clauses. + */ + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.NONE; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/FunctionalDependencyAnalysisSupport.java b/hibernate-core/src/main/java/org/hibernate/dialect/FunctionalDependencyAnalysisSupport.java new file mode 100644 index 0000000000..9325ca701d --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/FunctionalDependencyAnalysisSupport.java @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * Dialect support information for primary key functional dependency analysis + * within {@code GROUP BY} and {@code ORDER BY} clauses. + * + * @author Marco Belladelli + */ +public interface FunctionalDependencyAnalysisSupport { + /** + * Supports primary key functional dependency analysis + */ + boolean supportsAnalysis(); + + /** + * Supports functional dependency analysis through joined tables and result sets (e.g. unions) + */ + boolean supportsTableGroups(); + + /** + * Also supports functional dependency analysis for constant values other than table columns + */ + boolean supportsConstants(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/FunctionalDependencyAnalysisSupportImpl.java b/hibernate-core/src/main/java/org/hibernate/dialect/FunctionalDependencyAnalysisSupportImpl.java new file mode 100644 index 0000000000..63a39662d4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/FunctionalDependencyAnalysisSupportImpl.java @@ -0,0 +1,78 @@ +/* + * 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; + +/** + * @author Marco Belladelli + */ +public class FunctionalDependencyAnalysisSupportImpl implements FunctionalDependencyAnalysisSupport { + private final boolean supportsAnalysis; + private final boolean supportsTableGroups; + private final boolean supportsConstants; + + /** + * No support for functional dependency analysis + */ + public static final FunctionalDependencyAnalysisSupportImpl NONE = new FunctionalDependencyAnalysisSupportImpl( + false, + false, + false + ); + + /** + * Only supports the analysis for a single table reference, i.e. no support for joins / unions + */ + public static final FunctionalDependencyAnalysisSupportImpl TABLE_REFERENCE = new FunctionalDependencyAnalysisSupportImpl( + true, + false, + false + ); + + /** + * Supports the analysis for single tables, a group of joined tables or a result set (e.g. union) + * as long as only table columns are selected, i.e. no constants (see {@link #TABLE_GROUP_AND_CONSTANTS}) + */ + public static final FunctionalDependencyAnalysisSupportImpl TABLE_GROUP = new FunctionalDependencyAnalysisSupportImpl( + true, + true, + false + ); + + /** + * Fully supports the analysis for joined / union table groups, including any constant value + * (e.g. the literal {@code clazz_} column used as table per class inheritance discriminator column) + */ + public static final FunctionalDependencyAnalysisSupportImpl TABLE_GROUP_AND_CONSTANTS = new FunctionalDependencyAnalysisSupportImpl( + true, + true, + true + ); + + public FunctionalDependencyAnalysisSupportImpl( + boolean supportsAnalysis, + boolean supportsTableGroups, + boolean supportsConstants) { + this.supportsAnalysis = supportsAnalysis; + this.supportsTableGroups = supportsTableGroups; + this.supportsConstants = supportsConstants; + } + + @Override + public boolean supportsAnalysis() { + return supportsAnalysis; + } + + @Override + public boolean supportsTableGroups() { + return supportsTableGroups; + } + + @Override + public boolean supportsConstants() { + return supportsConstants; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java index c8ac546e60..a9cfea60bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -69,11 +69,11 @@ import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2 import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; import org.hibernate.type.descriptor.jdbc.H2FormatJsonJdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.TimeAsTimestampWithTimeZoneJdbcType; import org.hibernate.type.descriptor.jdbc.TimeUtcAsJdbcTimeJdbcType; -import org.hibernate.type.descriptor.jdbc.TimestampUtcAsInstantJdbcType; -import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.TimeUtcAsOffsetTimeJdbcType; +import org.hibernate.type.descriptor.jdbc.TimestampUtcAsInstantJdbcType; import org.hibernate.type.descriptor.jdbc.UUIDJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; @@ -823,6 +823,11 @@ public class H2Dialect extends Dialect { return getVersion().isSameOrAfter( 1, 4, 198 ); } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_GROUP_AND_CONSTANTS; + } + @Override public IdentityColumnSupport getIdentityColumnSupport() { return getVersion().isSameOrAfter( 2 ) 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 134a1afccf..3cf02c7e26 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -605,6 +605,11 @@ public class HSQLDialect extends Dialect { return false; } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_REFERENCE; + } + // Do not drop constraints explicitly, just do this by cascading instead. @Override public boolean dropConstraints() { diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java index b24392f172..42b4294963 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MariaDBDialect.java @@ -248,6 +248,11 @@ public class MariaDBDialect extends MySQLDialect { return getVersion().isSameOrAfter( 10, 5 ); } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_GROUP_AND_CONSTANTS; + } + @Override public IdentifierHelper buildIdentifierHelper(IdentifierHelperBuilder builder, DatabaseMetaData dbMetaData) throws SQLException { 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 87563d9e63..85f384048f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -1470,6 +1470,11 @@ public class MySQLDialect extends Dialect { return getMySQLVersion().isSameOrAfter( 8 ); } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_GROUP; + } + @Override public boolean canDisableConstraints() { return true; 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 69fd07ca38..eeece6d36e 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -78,7 +78,6 @@ import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; import org.hibernate.type.JavaObjectType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; -import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.BlobJdbcType; import org.hibernate.type.descriptor.jdbc.ClobJdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType; @@ -99,7 +98,34 @@ import jakarta.persistence.TemporalType; import static org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor.extractUsingTemplate; import static org.hibernate.query.sqm.TemporalUnit.DAY; import static org.hibernate.query.sqm.TemporalUnit.EPOCH; -import static org.hibernate.type.SqlTypes.*; +import static org.hibernate.type.SqlTypes.ARRAY; +import static org.hibernate.type.SqlTypes.BINARY; +import static org.hibernate.type.SqlTypes.BLOB; +import static org.hibernate.type.SqlTypes.CHAR; +import static org.hibernate.type.SqlTypes.CLOB; +import static org.hibernate.type.SqlTypes.FLOAT; +import static org.hibernate.type.SqlTypes.GEOGRAPHY; +import static org.hibernate.type.SqlTypes.GEOMETRY; +import static org.hibernate.type.SqlTypes.INET; +import static org.hibernate.type.SqlTypes.JSON; +import static org.hibernate.type.SqlTypes.LONG32NVARCHAR; +import static org.hibernate.type.SqlTypes.LONG32VARBINARY; +import static org.hibernate.type.SqlTypes.LONG32VARCHAR; +import static org.hibernate.type.SqlTypes.NCHAR; +import static org.hibernate.type.SqlTypes.NCLOB; +import static org.hibernate.type.SqlTypes.NVARCHAR; +import static org.hibernate.type.SqlTypes.OTHER; +import static org.hibernate.type.SqlTypes.SQLXML; +import static org.hibernate.type.SqlTypes.STRUCT; +import static org.hibernate.type.SqlTypes.TIME; +import static org.hibernate.type.SqlTypes.TIMESTAMP; +import static org.hibernate.type.SqlTypes.TIMESTAMP_UTC; +import static org.hibernate.type.SqlTypes.TIMESTAMP_WITH_TIMEZONE; +import static org.hibernate.type.SqlTypes.TIME_UTC; +import static org.hibernate.type.SqlTypes.TINYINT; +import static org.hibernate.type.SqlTypes.UUID; +import static org.hibernate.type.SqlTypes.VARBINARY; +import static org.hibernate.type.SqlTypes.VARCHAR; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsDate; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsLocalTime; import static org.hibernate.type.descriptor.DateTimeUtils.appendAsTime; @@ -1308,6 +1334,11 @@ public class PostgreSQLDialect extends Dialect { return false; } + @Override + public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { + return FunctionalDependencyAnalysisSupportImpl.TABLE_REFERENCE; + } + @Override public RowLockStrategy getWriteRowLockStrategy() { return RowLockStrategy.TABLE; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java index a700193093..8f839e64b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java @@ -11,6 +11,8 @@ import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.FunctionalDependencyAnalysisSupport; import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.EntityAssociationMapping; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; @@ -23,6 +25,7 @@ import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.ValuedModelPart; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.persister.entity.UnionSubclassEntityPersister; import org.hibernate.query.derived.AnonymousTupleEntityValuedModelPart; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; @@ -49,6 +52,7 @@ import org.hibernate.sql.results.graph.Fetchable; public class EntityValuedPathInterpretation extends AbstractSqmPathInterpretation implements SqlTupleContainer, Assignable { + private final Expression sqlExpression; public static EntityValuedPathInterpretation from( SqmEntityValuedSimplePath sqmPath, @@ -266,7 +270,7 @@ public class EntityValuedPathInterpretation extends AbstractSqmPathInterpreta } else { // When the table group is selected and the navigablePath is selected we need to expand - // to all columns, as we also expand this to all columns in the select clause + // to all columns, as we must make sure we include all columns present in the select clause expandToAllColumns = isSelected( tableGroup, navigablePath, querySpec ); } } @@ -277,33 +281,38 @@ public class EntityValuedPathInterpretation extends AbstractSqmPathInterpreta final SqlExpressionResolver sqlExprResolver = sqlAstCreationState.getSqlExpressionResolver(); final Expression sqlExpression; if ( expandToAllColumns ) { - // Expand to all columns of the entity mapping type, as we already did for the selection + // Expand to all columns of the entity mapping type to ensure a correct group / order by expression, + // or use only the primary key if the dialect supports functional dependency + final Dialect dialect = sqlAstCreationState.getCreationContext() + .getSessionFactory() + .getJdbcServices() + .getDialect(); final EntityMappingType entityMappingType = mapping.getEntityMappingType(); final EntityIdentifierMapping identifierMapping = entityMappingType.getIdentifierMapping(); - final EntityDiscriminatorMapping discriminatorMapping = entityMappingType.getDiscriminatorMapping(); - final int numberOfFetchables = entityMappingType.getNumberOfFetchables(); - final List expressions = new ArrayList<>( - numberOfFetchables + identifierMapping.getJdbcTypeCount() - + ( discriminatorMapping == null ? 0 : 1 ) - ); - final TableGroup parentTableGroup = tableGroup; + final List expressions = new ArrayList<>( identifierMapping.getJdbcTypeCount() ); final SelectableConsumer selectableConsumer = (selectionIndex, selectableMapping) -> { - final TableReference tableReference = parentTableGroup.resolveTableReference( + final TableReference tableReference = tableGroup.resolveTableReference( navigablePath, selectableMapping.getContainingTableExpression() ); - expressions.add( - sqlExprResolver.resolveSqlExpression( tableReference, selectableMapping ) - ); + expressions.add( sqlExprResolver.resolveSqlExpression( tableReference, selectableMapping ) ); }; identifierMapping.forEachSelectable( selectableConsumer ); - if ( discriminatorMapping != null ) { - discriminatorMapping.forEachSelectable( selectableConsumer ); - } - for ( int i = 0; i < numberOfFetchables; i++ ) { - final Fetchable fetchable = entityMappingType.getFetchable( i ); - if ( fetchable.isSelectable() ) { - fetchable.forEachSelectable( selectableConsumer ); + if ( !supportsFunctionalDependency( dialect, entityMappingType ) ) { + final EntityDiscriminatorMapping discriminatorMapping = entityMappingType.getDiscriminatorMapping(); + if ( discriminatorMapping != null ) { + expressions.add( discriminatorMapping.resolveSqlExpression( + navigablePath, + discriminatorMapping.getUnderlyingJdbcMapping(), + tableGroup, + sqlAstCreationState + ) ); + } + for ( int i = 0; i < entityMappingType.getNumberOfFetchables(); i++ ) { + final Fetchable fetchable = entityMappingType.getFetchable( i ); + if ( fetchable.isSelectable() ) { + fetchable.forEachSelectable( selectableConsumer ); + } } } sqlExpression = new SqlTuple( expressions, entityMappingType ); @@ -373,7 +382,21 @@ public class EntityValuedPathInterpretation extends AbstractSqmPathInterpreta return false; } - private final Expression sqlExpression; + private static boolean supportsFunctionalDependency(Dialect dialect, EntityMappingType entityMappingType) { + final FunctionalDependencyAnalysisSupport analysisSupport = dialect.getFunctionalDependencyAnalysisSupport(); + if ( analysisSupport.supportsAnalysis() ) { + if ( entityMappingType.getSqmMultiTableMutationStrategy() == null ) { + return true; + } + else { + return analysisSupport.supportsTableGroups() && ( analysisSupport.supportsConstants() || + // Union entity persisters use a literal 'clazz_' column as a discriminator + // that breaks functional dependency for dialects that don't support constants + !( entityMappingType.getEntityPersister() instanceof UnionSubclassEntityPersister ) ); + } + } + return false; + } public EntityValuedPathInterpretation( Expression sqlExpression, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceQueryGroupByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceQueryGroupByTest.java index b6affca637..3d8f12f6d3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceQueryGroupByTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceQueryGroupByTest.java @@ -6,6 +6,11 @@ */ package org.hibernate.orm.test.inheritance; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.FunctionalDependencyAnalysisSupport; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.persister.entity.UnionSubclassEntityPersister; + import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.Jira; @@ -22,8 +27,11 @@ import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; import static org.assertj.core.api.Assertions.assertThat; @@ -33,19 +41,34 @@ import static org.assertj.core.api.Assertions.assertThat; @SessionFactory( useCollectingStatementInspector = true ) @DomainModel( annotatedClasses = { InheritanceQueryGroupByTest.Parent.class, - InheritanceQueryGroupByTest.ChildOne.class, - InheritanceQueryGroupByTest.ChildTwo.class, - InheritanceQueryGroupByTest.MyEntity.class + InheritanceQueryGroupByTest.SingleTableParent.class, + InheritanceQueryGroupByTest.SingleTableChildOne.class, + InheritanceQueryGroupByTest.SingleTableChildTwo.class, + InheritanceQueryGroupByTest.JoinedParent.class, + InheritanceQueryGroupByTest.JoinedChildOne.class, + InheritanceQueryGroupByTest.JoinedChildTwo.class, + InheritanceQueryGroupByTest.TPCParent.class, + InheritanceQueryGroupByTest.TPCChildOne.class, + InheritanceQueryGroupByTest.TPCChildTwo.class, + InheritanceQueryGroupByTest.MyEntity.class, } ) @Jira( "https://hibernate.atlassian.net/browse/HHH-16349" ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-16773" ) public class InheritanceQueryGroupByTest { @BeforeAll public void setUp(SessionFactoryScope scope) { scope.inTransaction( session -> { - final ChildOne childOne = new ChildOne( "child_one", 1 ); - session.persist( childOne ); - session.persist( new MyEntity( 1, childOne ) ); - session.persist( new MyEntity( 2, childOne ) ); + final SingleTableChildOne st1 = new SingleTableChildOne(); + st1.setName( "single_table_child_one" ); + session.persist( st1 ); + final JoinedChildOne j1 = new JoinedChildOne(); + j1.setName( "joined_child_one" ); + session.persist( j1 ); + final TPCChildOne tpc1 = new TPCChildOne(); + tpc1.setName( "tpc_child_one" ); + session.persist( tpc1 ); + session.persist( new MyEntity( 1, st1, j1, tpc1 ) ); + session.persist( new MyEntity( 2, st1, j1, tpc1 ) ); } ); } @@ -53,82 +76,199 @@ public class InheritanceQueryGroupByTest { public void tearDown(SessionFactoryScope scope) { scope.inTransaction( session -> { session.createMutationQuery( "delete from MyEntity" ).executeUpdate(); - session.createMutationQuery( "delete from Parent" ).executeUpdate(); + session.createMutationQuery( "delete from " + Parent.class.getName() ).executeUpdate(); } ); } @Test - public void testGroupBy(SessionFactoryScope scope) { + public void testGroupBySingleTable(SessionFactoryScope scope) { + testGroupBy( scope, "singleTableParent", SingleTableParent.class, "single_table_child_one", 1 ); + } + + @Test + public void testGroupByJoined(SessionFactoryScope scope) { + testGroupBy( scope, "joinedParent", JoinedParent.class, "joined_child_one", 1 ); + } + + @Test + public void testGroupByTPC(SessionFactoryScope scope) { + testGroupBy( scope, "tpcParent", TPCParent.class, "tpc_child_one", 4 ); + } + + private void testGroupBy( + SessionFactoryScope scope, + String parentProp, + Class parentEntityClass, + String parentName, + int childPropCount) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); scope.inTransaction( session -> { final MyPojo myPojo = session.createQuery( - "select new " - + MyPojo.class.getName() - + "(sum(e.amount), re) from MyEntity e join e.parent re group by re", + String.format( + "select new %s(sum(e.amount), re) from MyEntity e join e.%s re group by re", + MyPojo.class.getName(), + parentProp + ), MyPojo.class ).getSingleResult(); assertThat( myPojo.getAmount() ).isEqualTo( 3L ); - assertThat( myPojo.getParent().getName() ).isEqualTo( "child_one" ); - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", 2 ); - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", 2 ); + assertThat( myPojo.getParent().getName() ).isEqualTo( parentName ); + final EntityMappingType entityMappingType = scope.getSessionFactory() + .getMappingMetamodel() + .findEntityDescriptor( parentEntityClass ); + final int expectedCount = supportsFunctionalDependency( scope, entityMappingType ) ? + childPropCount : + childPropCount + 1; + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", expectedCount ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", expectedCount ); } ); } @Test - public void testGroupByNotSelected(SessionFactoryScope scope) { + public void testGroupByNotSelectedSingleTable(SessionFactoryScope scope) { + testGroupByNotSelected( scope, "singleTableParent", "single_table_parent_id", 0 ); + } + + @Test + public void testGroupByNotSelectedJoined(SessionFactoryScope scope) { + testGroupByNotSelected( scope, "joinedParent", "joined_parent_id", 0 ); + } + + @Test + public void testGroupByNotSelectedTPC(SessionFactoryScope scope) { + testGroupByNotSelected( scope, "tpcParent", "tpc_parent_id", 3 ); + } + + private void testGroupByNotSelected( + SessionFactoryScope scope, + String parentProp, + String parentFkName, + int childPropCount) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); scope.inTransaction( session -> { final Long sum = session.createQuery( - "select sum(e.amount) from MyEntity e join e.parent re group by re", + String.format( "select sum(e.amount) from MyEntity e join e.%s re group by re", parentProp ), Long.class ).getSingleResult(); assertThat( sum ).isEqualTo( 3L ); // When not selected, group by should only use the foreign key (parent_id) - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "parent_id", 2 ); - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", 0 ); - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", 0 ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 2 ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", childPropCount ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", childPropCount ); } ); } @Test - public void testGroupByAndOrderBy(SessionFactoryScope scope) { + public void testGroupByAndOrderBySingleTable(SessionFactoryScope scope) { + testGroupByAndOrderBy( scope, "singleTableParent", SingleTableParent.class, "single_table_child_one", 1 ); + } + + @Test + public void testGroupByAndOrderByJoined(SessionFactoryScope scope) { + testGroupByAndOrderBy( scope, "joinedParent", JoinedParent.class, "joined_child_one", 1 ); + } + + @Test + public void testGroupByAndOrderByTPC(SessionFactoryScope scope) { + testGroupByAndOrderBy( scope, "tpcParent", TPCParent.class, "tpc_child_one", 4 ); + } + + private void testGroupByAndOrderBy( + SessionFactoryScope scope, + String parentProp, + Class parentEntityClass, + String parentName, + int childPropCount) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); scope.inTransaction( session -> { final MyPojo myPojo = session.createQuery( - "select new " - + MyPojo.class.getName() - + "(sum(e.amount), re) from MyEntity e join e.parent re group by re order by re", + String.format( + "select new %s(sum(e.amount), re) from MyEntity e join e.%s re group by re order by re", + MyPojo.class.getName(), + parentProp + ), MyPojo.class ).getSingleResult(); assertThat( myPojo.getAmount() ).isEqualTo( 3L ); - assertThat( myPojo.getParent().getName() ).isEqualTo( "child_one" ); - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", 3 ); - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", 3 ); + assertThat( myPojo.getParent().getName() ).isEqualTo( parentName ); + final EntityMappingType entityMappingType = scope.getSessionFactory() + .getMappingMetamodel() + .findEntityDescriptor( parentEntityClass ); + final int expectedCount = supportsFunctionalDependency( scope, entityMappingType ) ? + childPropCount : + childPropCount + 2; + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", expectedCount ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", expectedCount ); } ); } @Test - public void testGroupByAndOrderByNotSelected(SessionFactoryScope scope) { + public void testGroupByAndOrderByNotSelectedSingleTable(SessionFactoryScope scope) { + testGroupByAndOrderByNotSelected( scope, "singleTableParent", "single_table_parent_id", 0 ); + } + + @Test + public void testGroupByAndOrderByNotSelectedJoined(SessionFactoryScope scope) { + testGroupByAndOrderByNotSelected( scope, "joinedParent", "joined_parent_id", 0 ); + } + + @Test + public void testGroupByAndOrderByNotSelectedTPC(SessionFactoryScope scope) { + testGroupByAndOrderByNotSelected( scope, "tpcParent", "tpc_parent_id", 3 ); + } + + private void testGroupByAndOrderByNotSelected( + SessionFactoryScope scope, + String parentProp, + String parentFkName, + int childPropCount) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); scope.inTransaction( session -> { final Long sum = session.createQuery( - "select sum(e.amount) from MyEntity e join e.parent re group by re order by re", + String.format( + "select sum(e.amount) from MyEntity e join e.%s re group by re order by re", + parentProp + ), Long.class ).getSingleResult(); assertThat( sum ).isEqualTo( 3L ); - // When not selected, group by and order by should only use the foreign key (parent_id) - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "parent_id", 3 ); - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", 0 ); - statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", 0 ); + // When not selected, group by should only use the foreign key (parent_id) + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 3 ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", childPropCount ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", childPropCount ); } ); } - @Entity( name = "Parent" ) - @DiscriminatorColumn( name = "disc_col", discriminatorType = DiscriminatorType.INTEGER ) + private static Dialect getDialect(SessionFactoryScope scope) { + return scope.getSessionFactory().getJdbcServices().getDialect(); + } + + private static boolean supportsFunctionalDependency( + SessionFactoryScope scope, + EntityMappingType entityMappingType) { + final FunctionalDependencyAnalysisSupport analysisSupport = scope.getSessionFactory() + .getJdbcServices() + .getDialect() + .getFunctionalDependencyAnalysisSupport(); + if ( analysisSupport.supportsAnalysis() ) { + if ( entityMappingType.getSqmMultiTableMutationStrategy() == null ) { + return true; + } + else { + return analysisSupport.supportsTableGroups() && ( analysisSupport.supportsConstants() || + // Union entity persisters use a literal 'clazz_' column as a discriminator + // that breaks functional dependency for dialects that don't support constants + !( entityMappingType.getEntityPersister() instanceof UnionSubclassEntityPersister ) ); + } + } + return false; + } + + @MappedSuperclass public abstract static class Parent { @Id @GeneratedValue @@ -136,13 +276,6 @@ public class InheritanceQueryGroupByTest { private String name; - public Parent() { - } - - public Parent(String name) { - this.name = name; - } - public Long getId() { return id; } @@ -150,36 +283,64 @@ public class InheritanceQueryGroupByTest { public String getName() { return name; } + + public void setName(String name) { + this.name = name; + } } - @Entity( name = "ChildOne" ) + @Entity( name = "SingleTableParent" ) + @Inheritance( strategy = InheritanceType.SINGLE_TABLE ) + @DiscriminatorColumn( name = "disc_col", discriminatorType = DiscriminatorType.INTEGER ) + public static class SingleTableParent extends Parent { + } + + @Entity( name = "SingleTableChildOne" ) @DiscriminatorValue( "1" ) - public static class ChildOne extends Parent { + public static class SingleTableChildOne extends SingleTableParent { @Column( name = "child_one_col" ) private Integer childOneProp; - - public ChildOne() { - } - - public ChildOne(String name, Integer childOneProp) { - super( name ); - this.childOneProp = childOneProp; - } } - @Entity( name = "ChildTwo" ) + @Entity( name = "SingleTableChildTwo" ) @DiscriminatorValue( "2" ) - public static class ChildTwo extends Parent { + public static class SingleTableChildTwo extends SingleTableParent { @Column( name = "child_two_col" ) private Integer childTwoProp; + } - public ChildTwo() { - } + @Entity( name = "JoinedParent" ) + @Inheritance( strategy = InheritanceType.JOINED ) + public static class JoinedParent extends Parent { + } - public ChildTwo(String name, Integer childTwoProp) { - super( name ); - this.childTwoProp = childTwoProp; - } + @Entity( name = "JoinedChildOne" ) + public static class JoinedChildOne extends JoinedParent { + @Column( name = "child_one_col" ) + private Integer childOneProp; + } + + @Entity( name = "JoinedChildTwo" ) + public static class JoinedChildTwo extends JoinedParent { + @Column( name = "child_two_col" ) + private Integer childTwoProp; + } + + @Entity( name = "TPCParent" ) + @Inheritance( strategy = InheritanceType.TABLE_PER_CLASS ) + public static class TPCParent extends Parent { + } + + @Entity( name = "TPCChildOne" ) + public static class TPCChildOne extends TPCParent { + @Column( name = "child_one_col" ) + private Integer childOneProp; + } + + @Entity( name = "TPCChildTwo" ) + public static class TPCChildTwo extends TPCParent { + @Column( name = "child_two_col" ) + private Integer childTwoProp; } @Entity( name = "MyEntity" ) @@ -191,15 +352,29 @@ public class InheritanceQueryGroupByTest { private Integer amount; @ManyToOne - @JoinColumn( name = "parent_id" ) - private Parent parent; + @JoinColumn( name = "single_table_parent_id" ) + private SingleTableParent singleTableParent; + + @ManyToOne + @JoinColumn( name = "joined_parent_id" ) + private JoinedParent joinedParent; + + @ManyToOne + @JoinColumn( name = "tpc_parent_id" ) + private TPCParent tpcParent; public MyEntity() { } - public MyEntity(Integer amount, Parent parent) { + public MyEntity( + Integer amount, + SingleTableParent singleTableParent, + JoinedParent joinedParent, + TPCParent tpcParent) { this.amount = amount; - this.parent = parent; + this.singleTableParent = singleTableParent; + this.joinedParent = joinedParent; + this.tpcParent = tpcParent; } }