From 76275c70c9056e923ca3faa887b4d50e1f05f3fc Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 27 Jan 2023 10:31:41 +0100 Subject: [PATCH 0001/1497] Update branch names CI related files for 6.2 --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/contributor-build.yml | 4 ++-- README.adoc | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 71424cbd98e2..2e35aaf7cc30 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,10 +2,10 @@ name: "CodeQL" on: push: - branches: [ 'main' ] + branches: [ '6.2' ] pull_request: # The branches below must be a subset of the branches above - branches: [ 'main' ] + branches: [ '6.2' ] schedule: - cron: '34 11 * * 4' diff --git a/.github/workflows/contributor-build.yml b/.github/workflows/contributor-build.yml index acf7fb8a8276..78849a6b3825 100644 --- a/.github/workflows/contributor-build.yml +++ b/.github/workflows/contributor-build.yml @@ -9,10 +9,10 @@ name: Hibernate ORM build on: push: branches: - - 'main' + - '6.2' pull_request: branches: - - 'main' + - '6.2' permissions: {} # none diff --git a/README.adoc b/README.adoc index 17585d6a6e81..9b82d8b443aa 100644 --- a/README.adoc +++ b/README.adoc @@ -5,7 +5,7 @@ It also provides an implementation of the JPA specification, which is the standa This is the repository of its source code; see https://hibernate.org/orm/[Hibernate.org] for additional information. -image:https://ci.hibernate.org/job/hibernate-orm-pipeline/job/main/badge/icon[Build Status,link=https://ci.hibernate.org/job/hibernate-orm-pipeline/job/main/] +image:https://ci.hibernate.org/job/hibernate-orm-pipeline/job/6.2/badge/icon[Build Status,link=https://ci.hibernate.org/job/hibernate-orm-pipeline/job/6.2/] image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?logo=Gradle&labelColor=02303A[link=https://ge.hibernate.org/scans] == Continuous Integration From ebb8e26417768c641a1ad8f1a2cc331880948748 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 13 Dec 2022 12:50:41 +0100 Subject: [PATCH 0002/1497] HHH-15822 Add test for issue --- .../entity/AbstractTreeableEntityTest.java | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/persister/entity/AbstractTreeableEntityTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/persister/entity/AbstractTreeableEntityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/persister/entity/AbstractTreeableEntityTest.java new file mode 100644 index 000000000000..0fffd3f54600 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/persister/entity/AbstractTreeableEntityTest.java @@ -0,0 +1,105 @@ +/* + * 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.orm.test.persister.entity; + +import java.util.Collection; +import java.util.List; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToMany; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@DomainModel(annotatedClasses = AbstractTreeableEntityTest.TestEntity.class) +@SessionFactory +@TestForIssue(jiraKey = "HHH-15822") +public class AbstractTreeableEntityTest { + + @Test + public void testGenericParentQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> { + TestEntity entity = new TestEntity(); + entity.setName( "test" ); + session.persist( entity ); + + CriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery( TestEntity.class ); + Root root = query.from( TestEntity.class ); + + query.select( root ).where( cb.equal( root.get( "parent" ) , entity ) ); + List resultList = session.createQuery( query ).getResultList(); + + assertEquals( 0, resultList.size() ); + } ); + } + + @Entity(name = "TestEntity") + public static class TestEntity extends AbstractTreeableEntity { + } + + @MappedSuperclass + public abstract static class AbstractTreeableEntity> { + @Id + @GeneratedValue + private Long id; + + protected String name; + + @ManyToOne(fetch = FetchType.LAZY) + protected T parent; + + @OneToMany(cascade = CascadeType.REMOVE, mappedBy = "parent") + protected Collection children; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public T getParent() { + return parent; + } + + public void setParent(T parent) { + this.parent = parent; + } + + public Collection getChildren() { + return children; + } + + public void setChildren(Collection children) { + this.children = children; + } + } +} From 477f1b1f176a35234be724dc1685fd0846469a23 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 27 Jan 2023 10:23:01 +0100 Subject: [PATCH 0003/1497] HHH-15822 Update to HCANN 6.0.6.Final --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index f036e1d66b6e..7eaed8f983f2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -58,7 +58,7 @@ dependencyResolutionManagement { versionCatalogs { libs { version( "antlr", "4.10.1" ) - version( "hcann", "6.0.5.Final" ) + version( "hcann", "6.0.6.Final" ) version( "geolatte", "1.8.2" ) version( "byteBuddy", "1.12.18" ) version( "agroal", "2.0" ) From 69a5b1faab9c893577e29a70bdf2471c51eec073 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 13 Dec 2022 12:52:27 +0100 Subject: [PATCH 0004/1497] HHH-15822 Make sure MappedSuperclass can act as type for entity valued paths --- .../sqm/tree/domain/SqmEntityValuedSimplePath.java | 12 ++++++++++-- .../query/sqm/tree/domain/SqmFkExpression.java | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java index ef699ec16b86..09eeac8e0835 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmEntityValuedSimplePath.java @@ -63,10 +63,18 @@ public X accept(SemanticQueryWalker walker) { } @Override - public EntityDomainType getNodeType() { + public SqmPathSource getNodeType() { //noinspection unchecked - return (EntityDomainType) getReferencedPathSource().getSqmPathType(); + return (SqmPathSource) getReferencedPathSource().getSqmPathType(); } +// We can't expose that the type is a EntityDomainType because it could also be a MappedSuperclass +// Ideally, we would specify the return type to be IdentifiableDomainType, but that does not implement SqmPathSource yet +// and is hence incompatible with the return type of the super class +// @Override +// public EntityDomainType getNodeType() { +// //noinspection unchecked +// return (EntityDomainType) getReferencedPathSource().getSqmPathType(); +// } @Override public SqmTreatedSimplePath treatAs(Class treatJavaType) throws PathException { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFkExpression.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFkExpression.java index d1de261a7f71..717dc9807674 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFkExpression.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmFkExpression.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.tree.domain; +import org.hibernate.metamodel.model.domain.IdentifiableDomainType; import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SqmExpressible; @@ -24,7 +25,7 @@ public class SqmFkExpression extends AbstractSqmExpression { public SqmFkExpression(SqmEntityValuedSimplePath toOnePath, NodeBuilder criteriaBuilder) { //noinspection unchecked - super( (SqmExpressible) toOnePath.getNodeType().getIdType(), criteriaBuilder ); + super( (SqmExpressible) ( (IdentifiableDomainType) toOnePath.getNodeType() ).getIdType(), criteriaBuilder ); this.toOnePath = toOnePath; } From a6995b50a9a49a6fd4baa6e6c44b558e2d45ab2e Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Wed, 25 Jan 2023 22:21:23 +0100 Subject: [PATCH 0005/1497] HHH-16020 - Fix for error that surfaced in the LegacyOracleLimitHandler due to the fix for HHH-16020 Signed-off-by: Jan Schatteman --- .../dialect/pagination/LegacyOracleLimitHandler.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyOracleLimitHandler.java b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyOracleLimitHandler.java index 95adfb59774e..1921d1378466 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyOracleLimitHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/pagination/LegacyOracleLimitHandler.java @@ -61,6 +61,16 @@ public boolean supportsLimit() { return true; } + @Override + public boolean supportsOffset() { + return true; + } + + @Override + public boolean forceLimitUsage() { + return true; + } + @Override public boolean bindLimitParametersInReverseOrder() { return true; From 3281f4522e752c7146bc84932097b4e0fdb99486 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 26 Jan 2023 20:57:57 -0600 Subject: [PATCH 0006/1497] HHH-16110 - MERGE for optional table update PostgreSQL --- .../hibernate/dialect/H2SqlAstTranslator.java | 1 + .../hibernate/dialect/PostgreSQLDialect.java | 32 ++--- .../dialect/PostgreSQLSqlAstTranslator.java | 6 +- .../dialect/PostgresPlusDialect.java | 17 ++- .../dialect/SqlAstTranslatorWithMerge.java | 114 +++++++++++------- .../dialect/SqlAstTranslatorWithUpsert.java | 4 + 6 files changed, 102 insertions(+), 72 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java index 953a288343c2..afc887bb72ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2SqlAstTranslator.java @@ -297,6 +297,7 @@ protected String getFromDual() { return " from dual"; } + private boolean supportsOffsetFetchClause() { 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 552972101c2b..9a9e4eb36457 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -28,8 +28,8 @@ import org.hibernate.dialect.aggregate.AggregateSupport; import org.hibernate.dialect.aggregate.PostgreSQLAggregateSupport; import org.hibernate.dialect.function.CommonFunctionFactory; -import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction; import org.hibernate.dialect.function.PostgreSQLMinMaxFunction; +import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction; import org.hibernate.dialect.identity.IdentityColumnSupport; import org.hibernate.dialect.identity.PostgreSQLIdentityColumnSupport; import org.hibernate.dialect.pagination.LimitHandler; @@ -51,6 +51,7 @@ import org.hibernate.internal.util.JdbcExceptionHelper; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.persister.entity.mutation.EntityMutationTarget; import org.hibernate.procedure.internal.PostgreSQLCallableStatementSupport; import org.hibernate.procedure.spi.CallableStatementSupport; import org.hibernate.query.SemanticException; @@ -68,6 +69,8 @@ import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.exec.spi.JdbcOperation; +import org.hibernate.sql.model.MutationOperation; +import org.hibernate.sql.model.internal.OptionalTableUpdate; import org.hibernate.type.JavaObjectType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; @@ -1366,23 +1369,12 @@ public int rowIdSqlType() { return OTHER; } - // @Override -// public String getDisableConstraintsStatement() { -// return "set constraints all deferred"; -// } -// -// @Override -// public String getEnableConstraintsStatement() { -// return "set constraints all immediate"; -// } -// -// @Override -// public String getDisableConstraintStatement(String tableName, String name) { -// return "alter table " + tableName + " alter constraint " + name + " deferrable"; -// } -// -// @Override -// public String getEnableConstraintStatement(String tableName, String name) { -// return "alter table " + tableName + " alter constraint " + name + " deferrable"; -// } + @Override + public MutationOperation createOptionalTableUpdateOperation( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { + final PostgreSQLSqlAstTranslator translator = new PostgreSQLSqlAstTranslator<>( factory, optionalTableUpdate ); + return translator.createMergeOperation( optionalTableUpdate ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java index 50bae691f214..3c18a3d963ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLSqlAstTranslator.java @@ -6,11 +6,10 @@ */ package org.hibernate.dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.FetchClauseType; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.ast.tree.cte.CteMaterialization; import org.hibernate.sql.ast.tree.cte.CteStatement; @@ -33,7 +32,7 @@ * * @author Christian Beikov */ -public class PostgreSQLSqlAstTranslator extends AbstractSqlAstTranslator { +public class PostgreSQLSqlAstTranslator extends SqlAstTranslatorWithMerge { public PostgreSQLSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) { super( sessionFactory, statement ); @@ -280,5 +279,4 @@ public void visitBinaryArithmeticExpression(BinaryArithmeticExpression arithmeti arithmeticExpression.getRightHandOperand().accept( this ); appendSql( CLOSE_PARENTHESIS ); } - } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java index 68676119cef9..00e2c6f11b70 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgresPlusDialect.java @@ -11,14 +11,18 @@ import java.sql.SQLException; import java.sql.Types; -import jakarta.persistence.TemporalType; - import org.hibernate.boot.model.FunctionContributions; import org.hibernate.dialect.function.CommonFunctionFactory; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.persister.entity.mutation.EntityMutationTarget; import org.hibernate.query.sqm.CastType; import org.hibernate.query.sqm.TemporalUnit; +import org.hibernate.sql.model.MutationOperation; +import org.hibernate.sql.model.internal.OptionalTableUpdate; +import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation; +import jakarta.persistence.TemporalType; import static org.hibernate.query.sqm.TemporalUnit.DAY; @@ -112,4 +116,13 @@ public String getSelectGUIDString() { return "select uuid_generate_v1"; } + @Override + public MutationOperation createOptionalTableUpdateOperation( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { + // Postgres Plus does not support full merge semantics - + // https://www.enterprisedb.com/docs/migrating/oracle/oracle_epas_comparison/notable_differences/ + return new OptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithMerge.java b/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithMerge.java index a65c216a6339..128ef26ec7a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithMerge.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithMerge.java @@ -9,16 +9,22 @@ import java.util.List; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.StringHelper; import org.hibernate.sql.ast.spi.AbstractSqlAstTranslator; import org.hibernate.sql.ast.tree.Statement; import org.hibernate.sql.exec.spi.JdbcOperation; -import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.ast.ColumnValueBinding; import org.hibernate.sql.model.internal.OptionalTableUpdate; import org.hibernate.sql.model.jdbc.MergeOperation; /** - * Base for translators which support a full insert-or-update-or-delete (MERGE) command + * Base for translators which support a full insert-or-update-or-delete (MERGE) command. + *

+ * Use {@link #createMergeOperation(OptionalTableUpdate)} to translate an + * {@linkplain OptionalTableUpdate} into an executable {@linkplain MergeOperation} + * operation. + *

+ * * * @author Steve Ebersole */ @@ -28,9 +34,12 @@ public SqlAstTranslatorWithMerge(SessionFactoryImplementor sessionFactory, State } /** - * Create the MutationOperation for performing a MERGE + * Create the MutationOperation for performing a MERGE. + * + * The OptionalTableUpdate is {@linkplain #renderMergeStatement translated} + * and wrapped as a MutationOperation */ - public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableUpdate) { + public MergeOperation createMergeOperation(OptionalTableUpdate optionalTableUpdate) { renderMergeStatement( optionalTableUpdate ); return new MergeOperation( @@ -41,97 +50,110 @@ public MutationOperation createMergeOperation(OptionalTableUpdate optionalTableU ); } + /** + * Renders the OptionalTableUpdate as a MERGE query. + * + */ protected void renderMergeStatement(OptionalTableUpdate optionalTableUpdate) { - // template: // - // merge into [table] as t - // using values([bindings]) as s ([column-names]) - // on t.[key] = s.[key] + // merge into as t + // using (select col_1, col_2, ... from dual) as s + // on (t.key = s.key) // when not matched - // then insert ... + // then insert ... // when matched - // and s.[columns] is null - // then delete + // and s.col_1 is null + // and s.col_2 is null + // and ... + // then delete // when matched - // then update ... + // then update ... + // `merge into [as] t` renderMergeInto( optionalTableUpdate ); appendSql( " " ); + + // using (select col_1, col_2, ... from dual) as s renderMergeUsing( optionalTableUpdate ); appendSql( " " ); + + // on (t.key = s.key) renderMergeOn( optionalTableUpdate ); appendSql( " " ); + + // when not matched + // then insert ... renderMergeInsert( optionalTableUpdate ); appendSql( " " ); + + // when matched + // and s.col_1 is null + // and s.col_2 is null + // and ... + // then delete renderMergeDelete( optionalTableUpdate ); appendSql( " " ); + + // when matched + // then update ... renderMergeUpdate( optionalTableUpdate ); } protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) { appendSql( "merge into " ); + renderMergeTarget( optionalTableUpdate ); + } + + private void renderMergeTarget(OptionalTableUpdate optionalTableUpdate) { appendSql( optionalTableUpdate.getMutatingTable().getTableName() ); + appendSql( " " ); renderMergeTargetAlias(); } protected void renderMergeTargetAlias() { - appendSql( " as t" ); + appendSql( "as t" ); } protected void renderMergeUsing(OptionalTableUpdate optionalTableUpdate) { - appendSql( "using " ); + appendSql( "using (" ); + renderMergeUsingQuery( optionalTableUpdate ); + appendSql( ") " ); - renderMergeSource( optionalTableUpdate ); + renderMergeSourceAlias(); } - protected boolean wrapMergeSourceExpression() { - return true; + protected void renderMergeSourceAlias() { + appendSql( "as s" ); } - private void renderMergeSource(OptionalTableUpdate optionalTableUpdate) { - if ( wrapMergeSourceExpression() ) { - appendSql( " (" ); - } - + private void renderMergeUsingQuery(OptionalTableUpdate optionalTableUpdate) { final List valueBindings = optionalTableUpdate.getValueBindings(); final List keyBindings = optionalTableUpdate.getKeyBindings(); - final StringBuilder columnList = new StringBuilder(); - - appendSql( " values (" ); + appendSql( "select " ); for ( int i = 0; i < keyBindings.size(); i++ ) { - final ColumnValueBinding keyBinding = keyBindings.get( i ); if ( i > 0 ) { appendSql( ", " ); - columnList.append( ", " ); } - columnList.append( keyBinding.getColumnReference().getColumnExpression() ); - renderCasted( keyBinding.getValueExpression() ); + renderMergeUsingQuerySelection( keyBindings.get( i ) ); } for ( int i = 0; i < valueBindings.size(); i++ ) { appendSql( ", " ); - columnList.append( ", " ); - final ColumnValueBinding valueBinding = valueBindings.get( i ); - columnList.append( valueBinding.getColumnReference().getColumnExpression() ); - renderCasted( valueBinding.getValueExpression() ); + renderMergeUsingQuerySelection( valueBindings.get( i ) ); } - appendSql( ") " ); - - if ( wrapMergeSourceExpression() ) { - appendSql( ") " ); + final String selectionTable = StringHelper.nullIfEmpty( getFromDualForSelectOnly() ); + if ( selectionTable != null ) { + appendSql( " " ); + appendSql( selectionTable ); } - - renderMergeSourceAlias(); - - appendSql( "(" ); - appendSql( columnList.toString() ); - appendSql( ")" ); } - protected void renderMergeSourceAlias() { - appendSql( " as s" ); + protected void renderMergeUsingQuerySelection(ColumnValueBinding selectionBinding) { + renderCasted( selectionBinding.getValueExpression() ); + appendSql( " " ); + appendSql( selectionBinding.getColumnReference().getColumnExpression() ); } protected void renderMergeOn(OptionalTableUpdate optionalTableUpdate) { @@ -203,7 +225,7 @@ protected void renderMergeUpdate(OptionalTableUpdate optionalTableUpdate) { if ( i > 0 ) { appendSql( ", " ); } - binding.getColumnReference().appendColumnForWrite( this, "t" ); + binding.getColumnReference().appendColumnForWrite( this, null ); appendSql( "=" ); binding.getColumnReference().appendColumnForWrite( this, "s" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithUpsert.java b/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithUpsert.java index 7778292228ba..50988e8adc48 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithUpsert.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SqlAstTranslatorWithUpsert.java @@ -74,6 +74,10 @@ private void renderUpsertStatement(OptionalTableUpdate optionalTableUpdate) { protected void renderMergeInto(OptionalTableUpdate optionalTableUpdate) { appendSql( "merge into " ); + renderMergeTarget( optionalTableUpdate ); + } + + private void renderMergeTarget(OptionalTableUpdate optionalTableUpdate) { appendSql( optionalTableUpdate.getMutatingTable().getTableName() ); renderMergeTargetAlias(); } From 7564551f6e85f52076a7b7fcb7e68c507393c261 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 23 Aug 2022 14:47:31 +0200 Subject: [PATCH 0007/1497] HHH-15443 Allow JdbcType to wrap read and write expressions --- .../mapping/basic/JsonMappingTests.java | 99 ++++++-- .../mapping/basic/XmlMappingTests.java | 92 +++++-- .../dialect/CockroachLegacyDialect.java | 48 +++- .../dialect/MariaDBLegacyDialect.java | 8 +- .../community/dialect/MySQLLegacyDialect.java | 6 +- .../dialect/PostgreSQLLegacyDialect.java | 71 ++++-- .../process/spi/MetadataBuildingProcess.java | 20 +- .../hibernate/dialect/CockroachDialect.java | 31 ++- .../org/hibernate/dialect/MariaDBDialect.java | 8 +- .../dialect/MySQLCastingJsonJdbcType.java | 45 ++++ .../org/hibernate/dialect/MySQLDialect.java | 5 +- .../PostgreSQLCastingInetJdbcType.java | 111 +++++++++ ...stgreSQLCastingIntervalSecondJdbcType.java | 162 +++++++++++++ .../PostgreSQLCastingJsonJdbcType.java | 59 +++++ .../PostgreSQLCastingStructJdbcType.java | 91 +++++++ .../hibernate/dialect/PostgreSQLDialect.java | 37 +-- .../dialect/PostgreSQLStructJdbcType.java | 7 +- ...regateWindowEmulationQueryTransformer.java | 5 - .../org/hibernate/id/ExportableColumn.java | 6 + .../org/hibernate/mapping/Collection.java | 1 + .../java/org/hibernate/mapping/Column.java | 1 + .../java/org/hibernate/mapping/OneToMany.java | 1 + .../org/hibernate/mapping/Selectable.java | 17 ++ .../org/hibernate/mapping/SimpleValue.java | 1 + .../java/org/hibernate/mapping/Value.java | 63 +++++ .../internal/AbstractEmbeddableMapping.java | 2 +- .../internal/BasicAttributeMapping.java | 5 + ...CaseStatementDiscriminatorMappingImpl.java | 1 - .../internal/EmbeddableMappingTypeImpl.java | 2 +- .../internal/SelectableMappingImpl.java | 10 +- .../mapping/ordering/ast/ColumnReference.java | 1 - .../AbstractCollectionPersister.java | 3 +- .../entity/AbstractEntityPersister.java | 11 +- .../internal/cte/CteInsertHandler.java | 17 -- .../InPredicateRestrictionProducer.java | 1 - .../internal/inline/InlineUpdateHandler.java | 4 - .../ExecuteWithTemporaryTableHelper.java | 4 - .../temptable/InsertExecutionDelegate.java | 10 - .../temptable/TableBasedInsertHandler.java | 5 - .../sqm/sql/BaseSqmToSqlAstConverter.java | 8 +- .../SqlAstQueryPartProcessingStateImpl.java | 23 +- .../sql/ast/spi/AbstractSqlAstTranslator.java | 18 +- .../sql/ast/spi/SqlAstProcessingState.java | 4 + .../ast/tree/expression/ColumnReference.java | 24 +- .../sql/ast/tree/expression/Expression.java | 19 ++ .../type/descriptor/jdbc/JdbcType.java | 33 ++- .../descriptor/jdbc/JsonAsStringJdbcType.java | 170 +++++++++++++ .../descriptor/jdbc/XmlAsStringJdbcType.java | 224 ++++++++++++++---- .../cockroachdb/CockroachDbContributor.java | 13 +- .../AbstractCastingPostGISJdbcType.java | 161 +++++++++++++ .../postgis/PGCastingGeographyJdbcType.java | 38 +++ .../postgis/PGCastingGeometryJdbcType.java | 38 +++ .../postgis/PostgisDialectContributor.java | 11 +- 53 files changed, 1563 insertions(+), 292 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/MySQLCastingJsonJdbcType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingInetJdbcType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingIntervalSecondJdbcType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingJsonJdbcType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingStructJdbcType.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonAsStringJdbcType.java create mode 100644 hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/AbstractCastingPostGISJdbcType.java create mode 100644 hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGCastingGeographyJdbcType.java create mode 100644 hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGCastingGeometryJdbcType.java diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JsonMappingTests.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JsonMappingTests.java index 3eeb8186bb8c..af497f3d52dc 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JsonMappingTests.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/JsonMappingTests.java @@ -14,6 +14,10 @@ import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.entity.EntityPersister; @@ -26,6 +30,9 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import jakarta.persistence.Entity; @@ -36,6 +43,8 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isOneOf; +import static org.hamcrest.Matchers.isA; +import static org.hamcrest.Matchers.notNullValue; /** * @author Christian Beikov @@ -60,10 +69,37 @@ public Jackson() { } } - private final boolean supportsObjectMapKey; + private final Map stringMap; + private final Map objectMap; + private final List list; + private final String json; protected JsonMappingTests(boolean supportsObjectMapKey) { - this.supportsObjectMapKey = supportsObjectMapKey; + this.stringMap = Map.of( "name", "ABC" ); + this.objectMap = supportsObjectMapKey ? Map.of( + new StringNode( "name" ), + new StringNode( "ABC" ) + ) : null; + this.list = List.of( new StringNode( "ABC" ) ); + this.json = "{\"name\":\"abc\"}"; + } + + @BeforeEach + public void setup(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + session.persist( new EntityWithJson( 1, stringMap, objectMap, list, json ) ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + session.remove( session.find( EntityWithJson.class, 1 ) ); + } + ); } @Test @@ -74,39 +110,56 @@ public void verifyMappings(SessionFactoryScope scope) { final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( EntityWithJson.class ); final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry(); - final BasicAttributeMapping payloadAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "payload" ); - final BasicAttributeMapping objectMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "objectMap" ); - final BasicAttributeMapping listAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "list" ); + final BasicAttributeMapping stringMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( + "stringMap" ); + final BasicAttributeMapping objectMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( + "objectMap" ); + final BasicAttributeMapping listAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( + "list" ); final BasicAttributeMapping jsonAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "jsonString" ); - assertThat( payloadAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) ); + assertThat( stringMapAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) ); assertThat( objectMapAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) ); assertThat( listAttribute.getJavaType().getJavaTypeClass(), equalTo( List.class ) ); assertThat( jsonAttribute.getJavaType().getJavaTypeClass(), equalTo( String.class ) ); final JdbcType jsonType = jdbcTypeRegistry.getDescriptor( SqlTypes.JSON ); - assertThat( payloadAttribute.getJdbcMapping().getJdbcType(), is( jsonType ) ); - assertThat( objectMapAttribute.getJdbcMapping().getJdbcType(), is( jsonType ) ); - assertThat( listAttribute.getJdbcMapping().getJdbcType(), is( jsonType ) ); - assertThat( jsonAttribute.getJdbcMapping().getJdbcType(), is( jsonType ) ); - - Map stringMap = Map.of( "name", "ABC" ); - Map objectMap = supportsObjectMapKey ? Map.of( new StringNode( "name" ), new StringNode( "ABC" ) ) : null; - List list = List.of( new StringNode( "ABC" ) ); - String json = "{\"name\":\"abc\"}"; - // PostgreSQL returns the JSON slightly formatted - String alternativeJson = "{\"name\": \"abc\"}"; + assertThat( stringMapAttribute.getJdbcMapping().getJdbcType(), isA( (Class) jsonType.getClass() ) ); + assertThat( objectMapAttribute.getJdbcMapping().getJdbcType(), isA( (Class) jsonType.getClass() ) ); + assertThat( listAttribute.getJdbcMapping().getJdbcType(), isA( (Class) jsonType.getClass() ) ); + assertThat( jsonAttribute.getJdbcMapping().getJdbcType(), isA( (Class) jsonType.getClass() ) ); + } + @Test + public void verifyReadWorks(SessionFactoryScope scope) { scope.inTransaction( (session) -> { - session.persist( new EntityWithJson( 1, stringMap, objectMap, list, json ) ); + EntityWithJson entityWithJson = session.find( EntityWithJson.class, 1 ); + assertThat( entityWithJson.stringMap, is( stringMap ) ); + assertThat( entityWithJson.objectMap, is( objectMap ) ); + assertThat( entityWithJson.list, is( list ) ); } ); + } + @Test + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support comparing CLOBs with the = operator") + @SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "HANA doesn't support comparing LOBs with the = operator") + @SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true, reason = "Sybase doesn't support comparing LOBs with the = operator") + @SkipForDialect(dialectClass = OracleDialect.class, matchSubTypes = true, reason = "Oracle doesn't support comparing JSON with the = operator") + public void verifyComparisonWorks(SessionFactoryScope scope) { scope.inTransaction( (session) -> { - EntityWithJson entityWithJson = session.find( EntityWithJson.class, 1 ); - assertThat( entityWithJson.payload, is( stringMap ) ); + // PostgreSQL returns the JSON slightly formatted + String alternativeJson = "{\"name\": \"abc\"}"; + EntityWithJson entityWithJson = session.createQuery( + "from EntityWithJson e where e.stringMap = :param", + EntityWithJson.class + ) + .setParameter( "param", stringMap ) + .getSingleResult(); + assertThat( entityWithJson, notNullValue() ); + assertThat( entityWithJson.stringMap, is( stringMap ) ); assertThat( entityWithJson.objectMap, is( objectMap ) ); assertThat( entityWithJson.list, is( list ) ); assertThat( entityWithJson.jsonString, isOneOf( json, alternativeJson ) ); @@ -149,7 +202,7 @@ public static class EntityWithJson { //tag::basic-json-example[] @JdbcTypeCode( SqlTypes.JSON ) - private Map payload; + private Map stringMap; //end::basic-json-example[] @JdbcTypeCode( SqlTypes.JSON ) @@ -166,12 +219,12 @@ public EntityWithJson() { public EntityWithJson( Integer id, - Map payload, + Map stringMap, Map objectMap, List list, String jsonString) { this.id = id; - this.payload = payload; + this.stringMap = stringMap; this.objectMap = objectMap; this.list = list; this.jsonString = jsonString; diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/XmlMappingTests.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/XmlMappingTests.java index 98358a27fec6..dee266c2052e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/XmlMappingTests.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/XmlMappingTests.java @@ -11,6 +11,10 @@ import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.AbstractHANADialect; +import org.hibernate.dialect.DerbyDialect; +import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.entity.EntityPersister; @@ -23,6 +27,9 @@ import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; +import org.hibernate.testing.orm.junit.SkipForDialect; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import jakarta.persistence.Entity; @@ -34,6 +41,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isA; +import static org.hamcrest.Matchers.notNullValue; /** * @author Christian Beikov @@ -58,10 +67,36 @@ public Jackson() { } } - private final boolean supportsObjectMapKey; + + private final Map stringMap; + private final Map objectMap; + private final List list; protected XmlMappingTests(boolean supportsObjectMapKey) { - this.supportsObjectMapKey = supportsObjectMapKey; + this.stringMap = Map.of( "name", new StringNode( "ABC" ) ); + this.objectMap = supportsObjectMapKey ? Map.of( + new StringNode( "name" ), + new StringNode( "ABC" ) + ) : null; + this.list = List.of( new StringNode( "ABC" ) ); + } + + @BeforeEach + public void setup(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + session.persist( new EntityWithXml( 1, stringMap, objectMap, list ) ); + } + ); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + session.remove( session.find( EntityWithXml.class, 1 ) ); + } + ); } @Test @@ -69,30 +104,27 @@ public void verifyMappings(SessionFactoryScope scope) { final MappingMetamodelImplementor mappingMetamodel = scope.getSessionFactory() .getRuntimeMetamodels() .getMappingMetamodel(); - final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( EntityWithXml.class); + final EntityPersister entityDescriptor = mappingMetamodel.findEntityDescriptor( EntityWithXml.class ); final JdbcTypeRegistry jdbcTypeRegistry = mappingMetamodel.getTypeConfiguration().getJdbcTypeRegistry(); - final BasicAttributeMapping stringMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "stringMap" ); - final BasicAttributeMapping objectMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "objectMap" ); - final BasicAttributeMapping listAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( "list" ); + final BasicAttributeMapping stringMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( + "stringMap" ); + final BasicAttributeMapping objectMapAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( + "objectMap" ); + final BasicAttributeMapping listAttribute = (BasicAttributeMapping) entityDescriptor.findAttributeMapping( + "list" ); assertThat( stringMapAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) ); assertThat( objectMapAttribute.getJavaType().getJavaTypeClass(), equalTo( Map.class ) ); assertThat( listAttribute.getJavaType().getJavaTypeClass(), equalTo( List.class ) ); - final JdbcType xmlType = jdbcTypeRegistry.getDescriptor(SqlTypes.SQLXML); - assertThat( stringMapAttribute.getJdbcMapping().getJdbcType(), is( xmlType ) ); - assertThat( objectMapAttribute.getJdbcMapping().getJdbcType(), is( xmlType ) ); - assertThat( listAttribute.getJdbcMapping().getJdbcType(), is( xmlType ) ); - - Map stringMap = Map.of( "name", new StringNode( "ABC" ) ); - Map objectMap = supportsObjectMapKey ? Map.of( new StringNode( "name" ), new StringNode( "ABC" ) ) : null; - List list = List.of( new StringNode( "ABC" ) ); - scope.inTransaction( - (session) -> { - session.persist( new EntityWithXml( 1, stringMap, objectMap, list ) ); - } - ); + final JdbcType xmlType = jdbcTypeRegistry.getDescriptor( SqlTypes.SQLXML ); + assertThat( stringMapAttribute.getJdbcMapping().getJdbcType(), isA( (Class) xmlType.getClass() ) ); + assertThat( objectMapAttribute.getJdbcMapping().getJdbcType(), isA( (Class) xmlType.getClass() ) ); + assertThat( listAttribute.getJdbcMapping().getJdbcType(), isA( (Class) xmlType.getClass() ) ); + } + @Test + public void verifyReadWorks(SessionFactoryScope scope) { scope.inTransaction( (session) -> { EntityWithXml entityWithXml = session.find( EntityWithXml.class, 1 ); @@ -103,6 +135,28 @@ public void verifyMappings(SessionFactoryScope scope) { ); } + @Test + @SkipForDialect(dialectClass = DerbyDialect.class, reason = "Derby doesn't support comparing CLOBs with the = operator") + @SkipForDialect(dialectClass = AbstractHANADialect.class, matchSubTypes = true, reason = "HANA doesn't support comparing LOBs with the = operator") + @SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true, reason = "Sybase doesn't support comparing LOBs with the = operator") + @SkipForDialect(dialectClass = OracleDialect.class, matchSubTypes = true, reason = "Oracle doesn't support comparing JSON with the = operator") + public void verifyComparisonWorks(SessionFactoryScope scope) { + scope.inTransaction( + (session) -> { + EntityWithXml entityWithJson = session.createQuery( + "from EntityWithXml e where e.stringMap = :param", + EntityWithXml.class + ) + .setParameter( "param", stringMap ) + .getSingleResult(); + assertThat( entityWithJson, notNullValue() ); + assertThat( entityWithJson.stringMap, is( stringMap ) ); + assertThat( entityWithJson.objectMap, is( objectMap ) ); + assertThat( entityWithJson.list, is( list ) ); + } + ); + } + @Entity(name = "EntityWithXml") @Table(name = "EntityWithXml") public static class EntityWithXml { 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 979103fb78a7..5210cbe6614a 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 @@ -30,6 +30,9 @@ import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.NationalizationSupport; +import org.hibernate.dialect.PostgreSQLCastingInetJdbcType; +import org.hibernate.dialect.PostgreSQLCastingIntervalSecondJdbcType; +import org.hibernate.dialect.PostgreSQLCastingJsonJdbcType; import org.hibernate.dialect.PostgreSQLDriverKind; import org.hibernate.dialect.PostgreSQLInetJdbcType; import org.hibernate.dialect.PostgreSQLIntervalSecondJdbcType; @@ -228,19 +231,17 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR final DdlTypeRegistry ddlTypeRegistry = typeContributions.getTypeConfiguration().getDdlTypeRegistry(); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) ); - if ( PostgreSQLPGObjectJdbcType.isUsable() ) { - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) ); - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) ); - ddlTypeRegistry.addDescriptor( new Scale6IntervalSecondDdlType( this ) ); - - // Prefer jsonb if possible - if ( getVersion().isSameOrAfter( 20 ) ) { - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) ); - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); - } - else { - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) ); - } + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) ); + ddlTypeRegistry.addDescriptor( new Scale6IntervalSecondDdlType( this ) ); + + // Prefer jsonb if possible + if ( getVersion().isSameOrAfter( 20 ) ) { + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); + } + else { + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) ); } } @@ -338,6 +339,27 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLJsonJdbcType.INSTANCE ); } } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingIntervalSecondJdbcType.INSTANCE ); + if ( getVersion().isSameOrAfter( 20, 0 ) ) { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE ); + } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSON_INSTANCE ); + } + } + } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingIntervalSecondJdbcType.INSTANCE ); + if ( getVersion().isSameOrAfter( 20, 0 ) ) { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE ); + } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSON_INSTANCE ); + } } // Force Blob binding to byte[] for CockroachDB 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 14dfbfa03e32..ac2551f316d5 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 @@ -34,8 +34,10 @@ import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorMariaDBDatabaseImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; +import org.hibernate.type.SqlTypes; import org.hibernate.type.StandardBasicTypes; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.hibernate.type.descriptor.sql.internal.DdlTypeImpl; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; @@ -141,9 +143,11 @@ public JdbcType resolveSqlTypeDescriptor( @Override public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry(); + // Make sure we register the JSON type descriptor before calling super, because MariaDB does not need casting + jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE ); + super.contributeTypes( typeContributions, serviceRegistry ); - final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() - .getJdbcTypeRegistry(); if ( getVersion().isSameOrAfter( 10, 7 ) ) { jdbcTypeRegistry.addDescriptorIfAbsent( VarcharUUIDJdbcType.INSTANCE ); } 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 0cd1309022bb..183c02dd3ac7 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 @@ -21,6 +21,7 @@ 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; @@ -630,11 +631,10 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { super.contributeTypes( typeContributions, serviceRegistry ); - final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() - .getJdbcTypeRegistry(); + final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry(); if ( getMySQLVersion().isSameOrAfter( 5, 7 ) ) { - jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, MySQLCastingJsonJdbcType.INSTANCE ); } // MySQL requires a custom binder for binding untyped nulls with the NULL type 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 57cbfbdebb21..6bdc0bb54c80 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 @@ -30,9 +30,14 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.NationalizationSupport; import org.hibernate.dialect.OracleDialect; +import org.hibernate.dialect.PostgreSQLCastingInetJdbcType; +import org.hibernate.dialect.PostgreSQLCastingIntervalSecondJdbcType; +import org.hibernate.dialect.PostgreSQLCastingJsonJdbcType; +import org.hibernate.dialect.PostgreSQLCastingStructJdbcType; import org.hibernate.dialect.PostgreSQLDriverKind; import org.hibernate.dialect.PostgreSQLInetJdbcType; import org.hibernate.dialect.PostgreSQLIntervalSecondJdbcType; +import org.hibernate.dialect.PostgreSQLJsonJdbcType; import org.hibernate.dialect.PostgreSQLJsonbJdbcType; import org.hibernate.dialect.PostgreSQLPGObjectJdbcType; import org.hibernate.dialect.PostgreSQLStructJdbcType; @@ -253,21 +258,18 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR if ( getVersion().isSameOrAfter( 8, 2 ) ) { ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) ); } - if ( PostgreSQLPGObjectJdbcType.isUsable() ) { - // The following DDL types require that the PGobject class is usable/visible - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) ); - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) ); - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) ); - ddlTypeRegistry.addDescriptor( new Scale6IntervalSecondDdlType( this ) ); - - if ( getVersion().isSameOrAfter( 9, 2 ) ) { - // Prefer jsonb if possible - if ( getVersion().isSameOrAfter( 9, 4 ) ) { - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); - } - else { - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) ); - } + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) ); + ddlTypeRegistry.addDescriptor( new Scale6IntervalSecondDdlType( this ) ); + + if ( getVersion().isSameOrAfter( 9, 2 ) ) { + // Prefer jsonb if possible + if ( getVersion().isSameOrAfter( 9, 4 ) ) { + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); + } + else { + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "json", this ) ); } } } @@ -1334,13 +1336,48 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLIntervalSecondJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLStructJdbcType.INSTANCE ); } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingIntervalSecondJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingStructJdbcType.INSTANCE ); + } if ( getVersion().isSameOrAfter( 8, 2 ) ) { // HHH-9562 jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); if ( getVersion().isSameOrAfter( 9, 2 ) ) { - if ( PostgreSQLPGObjectJdbcType.isUsable() ) { - jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLJsonbJdbcType.INSTANCE ); + if ( getVersion().isSameOrAfter( 9, 4 ) ) { + if ( PostgreSQLPGObjectJdbcType.isUsable() ) { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLJsonbJdbcType.INSTANCE ); + } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE ); + } + } + else { + if ( PostgreSQLPGObjectJdbcType.isUsable() ) { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLJsonJdbcType.INSTANCE ); + } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSON_INSTANCE ); + } + } + } + } + } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingIntervalSecondJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingStructJdbcType.INSTANCE ); + + if ( getVersion().isSameOrAfter( 8, 2 ) ) { + jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); + if ( getVersion().isSameOrAfter( 9, 2 ) ) { + if ( getVersion().isSameOrAfter( 9, 4 ) ) { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE ); + } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSON_INSTANCE ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java index 409d61704547..5f45248b1318 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/spi/MetadataBuildingProcess.java @@ -64,6 +64,7 @@ import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JsonAsStringJdbcType; import org.hibernate.type.descriptor.jdbc.JsonJdbcType; import org.hibernate.type.descriptor.jdbc.XmlAsStringJdbcType; import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; @@ -661,29 +662,14 @@ public void contributeAttributeConverter(Class. + */ +package org.hibernate.dialect; + +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.JsonJdbcType; + +/** + * @author Christian Beikov + */ +public class MySQLCastingJsonJdbcType extends JsonJdbcType { + /** + * Singleton access + */ + public static final JsonJdbcType INSTANCE = new MySQLCastingJsonJdbcType( null ); + + public MySQLCastingJsonJdbcType(EmbeddableMappingType embeddableMappingType) { + super( embeddableMappingType ); + } + + @Override + public AggregateJdbcType resolveAggregateJdbcType( + EmbeddableMappingType mappingType, + String sqlType, + RuntimeModelCreationContext creationContext) { + return new MySQLCastingJsonJdbcType( mappingType ); + } + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( "cast(" ); + appender.append( writeExpression ); + appender.append( " as json)" ); + } +} 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 93f8dbfc980f..cbf2fb928961 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -627,10 +627,9 @@ public void initializeFunctionRegistry(FunctionContributions functionContributio public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { super.contributeTypes( typeContributions, serviceRegistry ); - final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() - .getJdbcTypeRegistry(); + final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration().getJdbcTypeRegistry(); - jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, JsonJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( SqlTypes.JSON, MySQLCastingJsonJdbcType.INSTANCE ); // MySQL requires a custom binder for binding untyped nulls with the NULL type typeContributions.contributeJdbcType( NullJdbcType.INSTANCE ); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingInetJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingInetJdbcType.java new file mode 100644 index 000000000000..0221d437a31b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingInetJdbcType.java @@ -0,0 +1,111 @@ +/* + * 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 java.net.InetAddress; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +/** + * @author Christian Beikov + */ +public class PostgreSQLCastingInetJdbcType implements JdbcType { + + public static final PostgreSQLCastingInetJdbcType INSTANCE = new PostgreSQLCastingInetJdbcType(); + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( "cast(" ); + appender.append( writeExpression ); + appender.append( " as inet)" ); + } + + @Override + public int getJdbcTypeCode() { + return SqlTypes.VARBINARY; + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.INET; + } + + @Override + public String toString() { + return "InetSecondJdbcType"; + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaType) { + // No literal support for now + return null; + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setString( index, getStringValue( value, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setString( name, getStringValue( value, options ) ); + } + + private String getStringValue(X value, WrapperOptions options) { + return getJavaType().unwrap( value, InetAddress.class, options ).getHostAddress(); + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new BasicExtractor<>( javaType, this ) { + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return getObject( rs.getString( paramIndex ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return getObject( statement.getString( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return getObject( statement.getString( name ), options ); + } + + private X getObject(String inetString, WrapperOptions options) throws SQLException { + if ( inetString == null ) { + return null; + } + return getJavaType().wrap( inetString, options ); + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingIntervalSecondJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingIntervalSecondJdbcType.java new file mode 100644 index 000000000000..ecaa28ef6210 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingIntervalSecondJdbcType.java @@ -0,0 +1,162 @@ +/* + * 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 java.math.BigDecimal; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; +import org.hibernate.sql.ast.SqlAstTranslator; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.tree.expression.Expression; +import org.hibernate.sql.ast.tree.expression.SelfRenderingExpression; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AdjustableJdbcType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; + +/** + * @author Christian Beikov + */ +public class PostgreSQLCastingIntervalSecondJdbcType implements AdjustableJdbcType { + + public static final PostgreSQLCastingIntervalSecondJdbcType INSTANCE = new PostgreSQLCastingIntervalSecondJdbcType(); + + @Override + public JdbcType resolveIndicatedType(JdbcTypeIndicators indicators, JavaType domainJtd) { + final int scale; + if ( indicators.getColumnScale() == JdbcTypeIndicators.NO_COLUMN_SCALE ) { + scale = domainJtd.getDefaultSqlScale( + indicators.getTypeConfiguration() + .getServiceRegistry() + .getService( JdbcServices.class ) + .getDialect(), + this + ); + } + else { + scale = indicators.getColumnScale(); + } + if ( scale > 6 ) { + // Since the maximum allowed scale on PostgreSQL is 6 (microsecond precision), + // we have to switch to the numeric type if the value is greater + return indicators.getTypeConfiguration().getJdbcTypeRegistry().getDescriptor( SqlTypes.NUMERIC ); + } + return this; + } + + @Override + public Expression wrapTopLevelSelectionExpression(Expression expression) { + return new SelfRenderingExpression() { + @Override + public void renderToSql( + SqlAppender sqlAppender, + SqlAstTranslator walker, + SessionFactoryImplementor sessionFactory) { + sqlAppender.append( "extract(epoch from " ); + expression.accept( walker ); + sqlAppender.append( ')' ); + } + + @Override + public JdbcMappingContainer getExpressionType() { + return expression.getExpressionType(); + } + }; + } + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( '(' ); + appender.append( writeExpression ); + appender.append( "*interval'1 second)" ); + } + + @Override + public int getJdbcTypeCode() { + return SqlTypes.NUMERIC; + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.INTERVAL_SECOND; + } + + @Override + public String toString() { + return "IntervalSecondJdbcType"; + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaType) { + // No literal support for now + return null; + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setBigDecimal( index, getBigDecimalValue( value, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setBigDecimal( name, getBigDecimalValue( value, options ) ); + } + + private BigDecimal getBigDecimalValue(X value, WrapperOptions options) { + return getJavaType().unwrap( value, BigDecimal.class, options ).movePointLeft( 9 ); + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaType javaType) { + return new BasicExtractor<>( javaType, this ) { + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return getObject( rs.getBigDecimal( paramIndex ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return getObject( statement.getBigDecimal( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException { + return getObject( statement.getBigDecimal( name ), options ); + } + + private X getObject(BigDecimal bigDecimal, WrapperOptions options) throws SQLException { + if ( bigDecimal == null ) { + return null; + } + return getJavaType().wrap( bigDecimal.movePointRight( 9 ), options ); + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingJsonJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingJsonJdbcType.java new file mode 100644 index 000000000000..e9168110be23 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingJsonJdbcType.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 . + */ +package org.hibernate.dialect; + +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.JsonJdbcType; + +/** + * @author Christian Beikov + */ +public class PostgreSQLCastingJsonJdbcType extends JsonJdbcType { + + public static final PostgreSQLCastingJsonJdbcType JSON_INSTANCE = new PostgreSQLCastingJsonJdbcType( false, null ); + public static final PostgreSQLCastingJsonJdbcType JSONB_INSTANCE = new PostgreSQLCastingJsonJdbcType( true, null ); + + private final boolean jsonb; + + public PostgreSQLCastingJsonJdbcType(boolean jsonb, EmbeddableMappingType embeddableMappingType) { + super( embeddableMappingType ); + this.jsonb = jsonb; + } + + @Override + public int getDdlTypeCode() { + return SqlTypes.JSON; + } + + @Override + public AggregateJdbcType resolveAggregateJdbcType( + EmbeddableMappingType mappingType, + String sqlType, + RuntimeModelCreationContext creationContext) { + return new PostgreSQLCastingJsonJdbcType( jsonb, mappingType ); + } + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( "cast(" ); + appender.append( writeExpression ); + appender.append( " as " ); + if ( jsonb ) { + appender.append( "jsonb)" ); + } + else { + appender.append( "json)" ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingStructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingStructJdbcType.java new file mode 100644 index 000000000000..e411c6859022 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLCastingStructJdbcType.java @@ -0,0 +1,91 @@ +/* + * 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 java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.hibernate.boot.model.naming.Identifier; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; + +/** + * @author Christian Beikov + */ +public class PostgreSQLCastingStructJdbcType extends PostgreSQLStructJdbcType { + + public static final PostgreSQLCastingStructJdbcType INSTANCE = new PostgreSQLCastingStructJdbcType( null, null, null ); + + public PostgreSQLCastingStructJdbcType( + EmbeddableMappingType embeddableMappingType, + String typeName, + int[] orderMapping) { + super( embeddableMappingType, typeName, orderMapping ); + } + + @Override + public AggregateJdbcType resolveAggregateJdbcType( + EmbeddableMappingType mappingType, + String sqlType, + RuntimeModelCreationContext creationContext) { + return new PostgreSQLCastingStructJdbcType( + mappingType, + sqlType, + creationContext.getBootModel() + .getDatabase() + .getDefaultNamespace() + .locateUserDefinedType( Identifier.toIdentifier( sqlType ) ) + .getOrderMapping() + ); + } + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( "cast(" ); + appender.append( writeExpression ); + appender.append( " as " ); + appender.append( getTypeName() ); + appender.append( ')' ); + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final String stringValue = ( (PostgreSQLCastingStructJdbcType) getJdbcType() ).toString( + value, + getJavaType(), + options + ); + st.setString( index, stringValue ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final String stringValue = ( (PostgreSQLCastingStructJdbcType) getJdbcType() ).toString( + value, + getJavaType(), + options + ); + st.setString( name, stringValue ); + } + }; + } +} 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 9a9e4eb36457..fccf122fdd39 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -248,16 +248,13 @@ protected void registerColumnTypes(TypeContributions typeContributions, ServiceR ddlTypeRegistry.addDescriptor( new DdlTypeImpl( SQLXML, "xml", this ) ); ddlTypeRegistry.addDescriptor( new DdlTypeImpl( UUID, "uuid", this ) ); - if ( PostgreSQLPGObjectJdbcType.isUsable() ) { - // The following DDL types require that the PGobject class is usable/visible - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) ); - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) ); - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) ); - ddlTypeRegistry.addDescriptor( new Scale6IntervalSecondDdlType( this ) ); - - // Prefer jsonb if possible - ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); - } + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( INET, "inet", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOMETRY, "geometry", this ) ); + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( GEOGRAPHY, "geography", this ) ); + ddlTypeRegistry.addDescriptor( new Scale6IntervalSecondDdlType( this ) ); + + // Prefer jsonb if possible + ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) ); } @Override @@ -1316,17 +1313,27 @@ public void contributeTypes(TypeContributions typeContributions, ServiceRegistry jdbcTypeRegistry.addDescriptor( XmlJdbcType.INSTANCE ); if ( driverKind == PostgreSQLDriverKind.PG_JDBC ) { + // HHH-9562 + jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); if ( PostgreSQLPGObjectJdbcType.isUsable() ) { jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLInetJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLIntervalSecondJdbcType.INSTANCE ); jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLStructJdbcType.INSTANCE ); - } - - // HHH-9562 - jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); - if ( PostgreSQLPGObjectJdbcType.isUsable() ) { jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLJsonbJdbcType.INSTANCE ); } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingIntervalSecondJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingStructJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE ); + } + } + else { + jdbcTypeRegistry.addDescriptorIfAbsent( UUIDJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingInetJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingIntervalSecondJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingStructJdbcType.INSTANCE ); + jdbcTypeRegistry.addDescriptorIfAbsent( PostgreSQLCastingJsonJdbcType.JSONB_INSTANCE ); } // PostgreSQL requires a custom binder for binding untyped nulls as VARBINARY diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructJdbcType.java index 927f4fe24a14..ca4a0e48d6f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructJdbcType.java @@ -56,7 +56,7 @@ */ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType implements AggregateJdbcType { - public static final PostgreSQLStructJdbcType INSTANCE = new PostgreSQLStructJdbcType(); + public static final PostgreSQLStructJdbcType INSTANCE = new PostgreSQLStructJdbcType( null, null, null ); private static final DateTimeFormatter LOCAL_DATE_TIME; static { @@ -89,11 +89,6 @@ public class PostgreSQLStructJdbcType extends PostgreSQLPGObjectJdbcType impleme private final EmbeddableMappingType embeddableMappingType; private final ValueExtractor objectArrayExtractor; - private PostgreSQLStructJdbcType() { - // The default instance is for reading only and will return an Object[] - this( null, null, null ); - } - public PostgreSQLStructJdbcType(EmbeddableMappingType embeddableMappingType, String typeName, int[] orderMapping) { super( typeName, SqlTypes.STRUCT ); this.embeddableMappingType = embeddableMappingType; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/function/AggregateWindowEmulationQueryTransformer.java b/hibernate-core/src/main/java/org/hibernate/dialect/function/AggregateWindowEmulationQueryTransformer.java index 734c6d1a07f4..f0d6c7c76f17 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/function/AggregateWindowEmulationQueryTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/function/AggregateWindowEmulationQueryTransformer.java @@ -122,7 +122,6 @@ public QuerySpec transform( columnName, false, null, - null, mapping.getJdbcMapping() ); final Expression expression = subSelections.get( i ).getExpression(); @@ -190,7 +189,6 @@ public QuerySpec transform( columnName, false, null, - null, jdbcMapping ); final int subValuesPosition = subSelectClause.getSqlSelections().size(); @@ -252,7 +250,6 @@ protected X replaceExpression(X expression) { columnName, false, null, - null, jdbcMapping ); final int subValuesPosition = subSelectClause.getSqlSelections().size(); @@ -311,7 +308,6 @@ protected X replaceExpression(X expression) { columnName, false, null, - null, jdbcMapping ); final int subValuesPosition = subSelectClause.getSqlSelections().size(); @@ -368,7 +364,6 @@ protected X replaceExpression(X expression) { columnName, false, null, - null, mapping.getJdbcMapping() ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java b/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java index 8328bc02c1f1..7256075e9950 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java +++ b/hibernate-core/src/main/java/org/hibernate/id/ExportableColumn.java @@ -13,6 +13,7 @@ import org.hibernate.FetchMode; import org.hibernate.MappingException; import org.hibernate.boot.model.relational.Database; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.spi.Mapping; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.mapping.Column; @@ -186,6 +187,11 @@ public boolean isColumnInsertable(int index) { public boolean isColumnUpdateable(int index) { return true; } + + @Override + public MetadataBuildingContext getBuildingContext() { + return table.getIdentifierValue().getBuildingContext(); + } } public static class ColumnIterator implements Iterator { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java index 08dc774b5d52..55f79035840e 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Collection.java @@ -170,6 +170,7 @@ protected Collection(Collection original) { this.loaderName = original.loaderName; } + @Override public MetadataBuildingContext getBuildingContext() { return buildingContext; } 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 e07df5bbd559..dde3b634d3e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -560,6 +560,7 @@ public String getReadExpr(Dialect dialect) { return hasCustomRead() ? customRead : getQuotedName( dialect ); } + @Override public String getWriteExpr() { return customWrite != null && customWrite.length() > 0 ? customWrite : "?"; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java index 5ddc8891bf21..82c5aa5087bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java @@ -51,6 +51,7 @@ public Value copy() { return new OneToMany( this ); } + @Override public MetadataBuildingContext getBuildingContext() { return buildingContext; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Selectable.java b/hibernate-core/src/main/java/org/hibernate/mapping/Selectable.java index d8ec6dad37c0..cb5c41178743 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Selectable.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Selectable.java @@ -6,8 +6,12 @@ */ package org.hibernate.mapping; +import org.hibernate.Incubating; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.query.sqm.function.SqmFunctionRegistry; +import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.spi.TypeConfiguration; /** @@ -62,4 +66,17 @@ public interface Selectable { String getAlias(Dialect dialect, Table table); String getTemplate(Dialect dialect, TypeConfiguration typeConfiguration, SqmFunctionRegistry functionRegistry); + + @Incubating + default String getWriteExpr() { + final String customWriteExpression = getCustomWriteExpression(); + return customWriteExpression == null || customWriteExpression.isEmpty() + ? "?" + : customWriteExpression; + } + + @Incubating + default String getWriteExpr(JdbcMapping jdbcMapping, Dialect dialect) { + return jdbcMapping.getJdbcType().wrapWriteExpression( getWriteExpr(), dialect ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index c880f1ce9fb7..602bbce2d437 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -142,6 +142,7 @@ protected SimpleValue(SimpleValue original) { this.generator = original.generator; } + @Override public MetadataBuildingContext getBuildingContext() { return buildingContext; } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Value.java b/hibernate-core/src/main/java/org/hibernate/mapping/Value.java index 095b7b4436de..7183288b5732 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Value.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Value.java @@ -11,9 +11,15 @@ import java.util.List; import org.hibernate.FetchMode; +import org.hibernate.Incubating; import org.hibernate.MappingException; +import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.spi.Mapping; +import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.service.ServiceRegistry; +import org.hibernate.type.CompositeType; +import org.hibernate.type.EntityType; +import org.hibernate.type.MetaType; import org.hibernate.type.Type; /** @@ -72,6 +78,59 @@ default List getConstraintColumns() { Type getType() throws MappingException; + @Incubating + default JdbcMapping getSelectableType(Mapping factory, int index) throws MappingException { + return getType( factory, getType(), index ); + } + + private JdbcMapping getType(Mapping factory, Type elementType, int index) { + if ( elementType instanceof CompositeType ) { + final Type[] subtypes = ( (CompositeType) elementType ).getSubtypes(); + for ( int i = 0; i < subtypes.length; i++ ) { + final Type subtype = subtypes[i]; + final int columnSpan; + if ( subtype instanceof EntityType ) { + final EntityType entityType = (EntityType) subtype; + final Type idType = getIdType( entityType ); + columnSpan = idType.getColumnSpan( factory ); + } + else { + columnSpan = subtype.getColumnSpan( factory ); + } + if ( columnSpan < index ) { + index -= columnSpan; + } + else if ( columnSpan != 0 ) { + return getType( factory, subtype, index ); + } + } + // Should never happen + throw new IllegalStateException( "Type index is past the types column span!" ); + } + else if ( elementType instanceof EntityType ) { + final EntityType entityType = (EntityType) elementType; + final Type idType = getIdType( entityType ); + return getType( factory, idType, index ); + } + else if ( elementType instanceof MetaType ) { + return (JdbcMapping) ( (MetaType) elementType ).getBaseType(); + } + return (JdbcMapping) elementType; + } + + private Type getIdType(EntityType entityType) { + final PersistentClass entityBinding = getBuildingContext().getMetadataCollector() + .getEntityBinding( entityType.getAssociatedEntityName() ); + final Type idType; + if ( entityType.isReferenceToPrimaryKey() ) { + idType = entityBinding.getIdentifier().getType(); + } + else { + idType = entityBinding.getProperty( entityType.getRHSUniqueKeyPropertyName() ).getType(); + } + return idType; + } + FetchMode getFetchMode(); Table getTable(); @@ -105,6 +164,10 @@ default List getConstraintColumns() { boolean[] getColumnUpdateability(); boolean hasAnyUpdatableColumns(); + @Incubating + default MetadataBuildingContext getBuildingContext() { + throw new UnsupportedOperationException( "Value#getBuildingContext is not implemented by: " + getClass().getName() ); + } ServiceRegistry getServiceRegistry(); Value copy(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java index 2a35eb97538f..6f8e4456a318 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEmbeddableMapping.java @@ -306,7 +306,7 @@ protected static boolean finishInitialization( selectablePath, selectable.isFormula(), selectable.getCustomReadExpression(), - selectable.getCustomWriteExpression(), + selectable.getWriteExpr( ( (BasicType) subtype ).getJdbcMapping(), dialect ), columnDefinition, length, precision, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java index 19096823940b..2f56b9ec4bf5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java @@ -250,6 +250,11 @@ public String getCustomWriteExpression() { return customWriteExpression; } + @Override + public String getWriteExpression() { + return customWriteExpression; + } + @Override public String getColumnDefinition() { return columnDefinition; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java index 2710b4c014ee..c4e50c450f2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CaseStatementDiscriminatorMappingImpl.java @@ -150,7 +150,6 @@ public void renderToSql( tableDiscriminatorDetails.getCheckColumnName(), false, null, - null, getJdbcMapping() ), true diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java index f9db6b007a91..8ce82d214ef5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java @@ -420,7 +420,7 @@ private boolean finishInitialization( selectablePath, selectable.isFormula(), selectable.getCustomReadExpression(), - selectable.getCustomWriteExpression(), + selectable.getWriteExpr( ( (BasicType) subtype ).getJdbcMapping(), dialect ), columnDefinition, length, precision, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SelectableMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SelectableMappingImpl.java index b7bd17d1ce5d..b631bc48cdab 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SelectableMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SelectableMappingImpl.java @@ -16,6 +16,7 @@ import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectablePath; import org.hibernate.query.sqm.function.SqmFunctionRegistry; +import org.hibernate.type.BasicType; import org.hibernate.type.spi.TypeConfiguration; /** @@ -57,7 +58,7 @@ public SelectableMappingImpl( this.selectionExpression = selectionExpression.intern(); this.selectablePath = selectablePath == null ? new SelectablePath( selectionExpression ) : selectablePath; this.customReadExpression = customReadExpression == null ? null : customReadExpression.intern(); - this.customWriteExpression = customWriteExpression == null ? null : customWriteExpression.intern(); + this.customWriteExpression = customWriteExpression == null || isFormula ? null : customWriteExpression.intern(); this.nullable = nullable; this.insertable = insertable; this.updateable = updateable; @@ -160,7 +161,7 @@ public static SelectableMapping from( ? null : parentPath.append( selectableName ), selectable.getCustomReadExpression(), - selectable.getCustomWriteExpression(), + selectable.getWriteExpr( jdbcMapping, dialect ), columnDefinition, length, precision, @@ -214,6 +215,11 @@ public String getCustomWriteExpression() { return customWriteExpression; } + @Override + public String getWriteExpression() { + return customWriteExpression; + } + @Override public boolean isFormula() { return isFormula; 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 f54335ad6256..82f9f1081876 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 @@ -67,7 +67,6 @@ public Expression resolve( // because these ordering fragments are only ever part of the order-by clause, there // is no need for the JdbcMapping null, - null, null ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index 825c92d36e77..5d38da4c922a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -128,6 +128,7 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; import org.hibernate.sql.results.internal.SqlSelectionImpl; +import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; @@ -397,7 +398,7 @@ public AbstractCollectionPersister( else { Column col = (Column) selectable; elementColumnNames[j] = col.getQuotedName( dialect ); - elementColumnWriters[j] = col.getWriteExpr(); + elementColumnWriters[j] = col.getWriteExpr( elementBootDescriptor.getSelectableType( factory, j ), dialect ); elementColumnReaders[j] = col.getReadExpr( dialect ); elementColumnReaderTemplates[j] = col.getTemplate( dialect, diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index ff9f96572370..2ff5362444e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -662,7 +662,7 @@ else if ( batchSize > 1 ) { else { final Column column = (Column) selectable; colNames[k] = column.getQuotedName( dialect ); - colWriters[k] = column.getWriteExpr(); + colWriters[k] = column.getWriteExpr( prop.getValue().getSelectableType( factory, k ), dialect ); } } propertyColumnNames[i] = colNames; @@ -1326,7 +1326,6 @@ protected Predicate generateJoinPredicate( rootPkColumnName, false, null, - null, selection.getJdbcMapping() ) ); @@ -1339,7 +1338,6 @@ protected Predicate generateJoinPredicate( fkColumnName, false, null, - null, selection.getJdbcMapping() ) ); @@ -3062,7 +3060,6 @@ private Predicate createDiscriminatorPredicate( discriminatorExpression, isDiscriminatorFormula(), null, - null, discriminatorType.getJdbcMapping() ) ); @@ -5301,7 +5298,7 @@ private AttributeMapping generateNonIdAttributeMapping( null, false, null, - null, + "?", column.getSqlType(), column.getLength(), column.getPrecision(), @@ -5330,7 +5327,7 @@ private AttributeMapping generateNonIdAttributeMapping( attrColumnExpression = attrColumnNames[0]; isAttrColumnExpressionFormula = false; customReadExpr = null; - customWriteExpr = null; + customWriteExpr = "?"; Column column = value.getColumns().get( 0 ); columnDefinition = column.getSqlType(); length = column.getLength(); @@ -5356,7 +5353,7 @@ private AttributeMapping generateNonIdAttributeMapping( creationContext.getTypeConfiguration(), creationContext.getFunctionRegistry() ); - customWriteExpr = selectable.getCustomWriteExpression(); + customWriteExpr = selectable.getWriteExpr( (JdbcMapping) attrType, creationContext.getDialect() ); Column column = value.getColumns().get( 0 ); columnDefinition = column.getSqlType(); length = column.getLength(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java index 1d9c0b1b9ae8..7183acddb4da 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/cte/CteInsertHandler.java @@ -268,7 +268,6 @@ public int execute(DomainQueryExecutionContext executionContext) { rowNumberColumn.getColumnExpression(), false, null, - null, rowNumberColumn.getJdbcMapping() ); insertStatement.getTargetColumns().set( @@ -322,7 +321,6 @@ public int execute(DomainQueryExecutionContext executionContext) { columnReference.getColumnExpression(), false, null, - null, columnReference.getJdbcMapping() ) ) @@ -353,7 +351,6 @@ public int execute(DomainQueryExecutionContext executionContext) { rowNumberColumn.getColumnExpression(), false, null, - null, rowNumberColumn.getJdbcMapping() ); insertStatement.getTargetColumns().add( columnReference ); @@ -391,7 +388,6 @@ public int execute(DomainQueryExecutionContext executionContext) { rowNumberColumn.getColumnExpression(), false, null, - null, rowNumberColumn.getJdbcMapping() ); final CteColumn idColumn = fullEntityCteTable.getCteColumns().get( 0 ); @@ -498,7 +494,6 @@ public int execute(DomainQueryExecutionContext executionContext) { rowNumberColumn.getColumnExpression(), false, null, - null, rowNumberColumn.getJdbcMapping() ) ) @@ -515,7 +510,6 @@ public int execute(DomainQueryExecutionContext executionContext) { idColumn.getColumnExpression(), false, null, - null, idColumn.getJdbcMapping() ), BinaryArithmeticOperator.ADD, @@ -527,7 +521,6 @@ public int execute(DomainQueryExecutionContext executionContext) { rowNumberColumn.getColumnExpression(), false, null, - null, rowNumberColumn.getJdbcMapping() ), integerType @@ -560,7 +553,6 @@ public int execute(DomainQueryExecutionContext executionContext) { cteColumn.getColumnExpression(), false, null, - null, cteColumn.getJdbcMapping() ) ) @@ -822,7 +814,6 @@ protected String addDmlCtes( rowNumberColumn.getColumnExpression(), false, null, - null, rowNumberColumn.getJdbcMapping() ); // Insert in the same order as the original tuples came @@ -843,7 +834,6 @@ protected String addDmlCtes( keyColumns[j], false, null, - null, null ) ); @@ -878,7 +868,6 @@ protected String addDmlCtes( rowNumberColumn.getColumnExpression(), false, null, - null, rowNumberColumn.getJdbcMapping() ) ) @@ -897,7 +886,6 @@ protected String addDmlCtes( idCteColumn.getColumnExpression(), false, null, - null, idCteColumn.getJdbcMapping() ) ) @@ -914,7 +902,6 @@ protected String addDmlCtes( cteColumn.getColumnExpression(), false, null, - null, cteColumn.getJdbcMapping() ) ) @@ -944,7 +931,6 @@ protected String addDmlCtes( idCteColumn.getColumnExpression(), false, null, - null, idCteColumn.getJdbcMapping() ); finalResultQuery.getSelectClause().addSqlSelection( @@ -998,7 +984,6 @@ protected String addDmlCtes( keyColumns[j], false, null, - null, null ) ); @@ -1011,7 +996,6 @@ protected String addDmlCtes( rootKeyColumns[j], false, null, - null, null ) ) @@ -1046,7 +1030,6 @@ protected String addDmlCtes( entry.getKey().get( j ).getColumnExpression(), columnReference.isColumnExpressionFormula(), null, - null, columnReference.getJdbcMapping() ) ) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InPredicateRestrictionProducer.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InPredicateRestrictionProducer.java index bc625b2093ef..d316e8b59f6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InPredicateRestrictionProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InPredicateRestrictionProducer.java @@ -74,7 +74,6 @@ public InListPredicate produceRestriction( // id columns cannot be formulas and cannot have custom read and write expressions false, null, - null, basicIdMapping.getJdbcMapping() ); predicate = new InListPredicate( inFixture ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java index 1fc0c85cb082..0d8346847206 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/inline/InlineUpdateHandler.java @@ -452,7 +452,6 @@ private void updateTable( columnReference.getColumnExpression(), false, null, - null, columnReference.getJdbcMapping() ); columnNames.add( columnReference.getColumnExpression() ); @@ -463,7 +462,6 @@ private void updateTable( selectableMapping.getSelectionExpression(), false, null, - null, columnReference.getJdbcMapping() ) ); @@ -485,7 +483,6 @@ private void updateTable( columnReference.getColumnExpression(), false, null, - null, columnReference.getJdbcMapping() ); columnNames = Collections.singletonList( columnReference.getColumnExpression() ); @@ -497,7 +494,6 @@ private void updateTable( ( (BasicEntityIdentifierMapping) entityDescriptor.getIdentifierMapping() ).getSelectionExpression(), false, null, - null, columnReference.getJdbcMapping() ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java index 6e816ef6599d..3c66bd9984b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/ExecuteWithTemporaryTableHelper.java @@ -80,7 +80,6 @@ public static int saveMatchingIdsIntoIdTable( // id columns cannot be formulas and cannot have custom read and write expressions false, null, - null, column.getJdbcMapping() ) ); @@ -230,7 +229,6 @@ private static void applyIdTableSelections( temporaryTableColumn.getColumnName(), false, null, - null, temporaryTableColumn.getJdbcMapping() ) ) @@ -250,7 +248,6 @@ private static void applyIdTableSelections( selectableMapping.getSelectionExpression(), false, null, - null, selectableMapping.getJdbcMapping() ) ) @@ -274,7 +271,6 @@ private static void applyIdTableRestrictions( idTable.getSessionUidColumn().getColumnName(), false, null, - null, idTable.getSessionUidColumn().getJdbcMapping() ), ComparisonOperator.EQUAL, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java index 602d54d1d23e..7dc59d9fd2c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/InsertExecutionDelegate.java @@ -22,7 +22,6 @@ import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.generator.EventType; import org.hibernate.id.BulkInsertionCapableIdentifierGenerator; import org.hibernate.id.OptimizableGenerator; import org.hibernate.id.PostInsertIdentityPersister; @@ -348,7 +347,6 @@ private void insertRootTable( columnReference.getColumnExpression(), false, null, - null, columnReference.getJdbcMapping() ) ) @@ -369,7 +367,6 @@ private void insertRootTable( TemporaryTable.ENTITY_TABLE_IDENTITY_COLUMN, false, null, - null, identifierMapping.getJdbcMapping() ); idSelectQuerySpec.getSelectClause() @@ -453,7 +450,6 @@ private void insertRootTable( sessionUidColumn.getColumnName(), false, null, - null, sessionUidColumn.getJdbcMapping() ), ComparisonOperator.EQUAL, @@ -470,7 +466,6 @@ private void insertRootTable( rowNumberColumn.getColumnName(), false, null, - null, rowNumberColumn.getJdbcMapping() ), ComparisonOperator.EQUAL, @@ -529,7 +524,6 @@ private void insertRootTable( keyColumns[0], false, null, - null, identifierMapping.getJdbcMapping() ) ); @@ -542,7 +536,6 @@ private void insertRootTable( idColumnReference.getColumnExpression(), false, null, - null, idColumnReference.getJdbcMapping() ) ) @@ -599,7 +592,6 @@ public Object getEntity() { TemporaryTable.ENTITY_TABLE_IDENTITY_COLUMN, false, null, - null, identifierMapping.getJdbcMapping() ), ComparisonOperator.EQUAL, @@ -706,7 +698,6 @@ private void insertTable( columnReference.getColumnExpression(), false, null, - null, columnReference.getJdbcMapping() ) ) @@ -740,7 +731,6 @@ else if ( identifierGenerator instanceof OptimizableGenerator ) { targetKeyColumnName, false, null, - null, identifierMapping.getJdbcMapping() ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java index 8731d8bce018..970ddba12ec4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/temptable/TableBasedInsertHandler.java @@ -189,7 +189,6 @@ private ExecutionDelegate resolveDelegate(DomainQueryExecutionContext executionC rowNumberColumn.getColumnName(), false, null, - null, rowNumberColumn.getJdbcMapping() ); insertStatement.getTargetColumns().set( @@ -214,7 +213,6 @@ else if ( entityDescriptor.getGenerator() instanceof OptimizableGenerator ) { rowNumberColumn.getColumnName(), false, null, - null, rowNumberColumn.getJdbcMapping() ); insertStatement.getTargetColumns().add( columnReference ); @@ -237,7 +235,6 @@ else if ( entityDescriptor.getGenerator() instanceof OptimizableGenerator ) { sessionUidColumn.getColumnName(), false, null, - null, sessionUidColumn.getJdbcMapping() ); insertStatement.getTargetColumns().add( sessionUidColumnReference ); @@ -267,7 +264,6 @@ else if ( entityDescriptor.getGenerator() instanceof OptimizableGenerator ) { rowNumberColumn.getColumnName(), false, null, - null, rowNumberColumn.getJdbcMapping() ); insertStatement.getTargetColumns().add( columnReference ); @@ -286,7 +282,6 @@ else if ( entityDescriptor.getGenerator() instanceof OptimizableGenerator ) { sessionUidColumn.getColumnName(), false, null, - null, sessionUidColumn.getJdbcMapping() ); insertStatement.getTargetColumns().add( sessionUidColumnReference ); 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 9eecee0361df..2af562ee1896 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 @@ -96,6 +96,8 @@ import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPath; import org.hibernate.metamodel.model.domain.internal.AnyDiscriminatorSqmPathSource; +import org.hibernate.query.derived.AnonymousTupleTableGroupProducer; +import org.hibernate.query.derived.AnonymousTupleType; import org.hibernate.metamodel.model.domain.internal.BasicSqmPathSource; import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource; import org.hibernate.metamodel.model.domain.internal.DiscriminatorSqmPath; @@ -113,8 +115,6 @@ import org.hibernate.query.criteria.JpaPath; import org.hibernate.query.criteria.JpaSearchOrder; import org.hibernate.query.derived.AnonymousTupleEntityValuedModelPart; -import org.hibernate.query.derived.AnonymousTupleTableGroupProducer; -import org.hibernate.query.derived.AnonymousTupleType; import org.hibernate.query.spi.QueryEngine; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBinding; @@ -4611,7 +4611,6 @@ else if ( collectionPart instanceof ManyToManyCollectionPart ) { columnNames.get( i ), false, null, - null, subQueryColumns.get( i ).getJdbcMapping() ) ); @@ -4654,7 +4653,6 @@ else if ( collectionPart instanceof ManyToManyCollectionPart ) { columnNames.get( 0 ), false, null, - null, expression.getExpressionType().getSingleJdbcMapping() ) ); @@ -4757,7 +4755,6 @@ else if ( modelPart instanceof EmbeddableValuedModelPart ) { tableReference.getColumnNames().get( 0 ), false, null, - null, sqlSelections.get( 0 ).getExpressionType().getSingleJdbcMapping() ) ), @@ -4774,7 +4771,6 @@ else if ( modelPart instanceof EmbeddableValuedModelPart ) { tableReference.getColumnNames().get( selectionIndex ), false, null, - null, selectionMapping.getJdbcMapping() ) ) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQueryPartProcessingStateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQueryPartProcessingStateImpl.java index 35d5e59098b0..e91a652c30ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQueryPartProcessingStateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/SqlAstQueryPartProcessingStateImpl.java @@ -147,12 +147,23 @@ else if ( fetchParent != null ) { final SelectClause selectClause = ( (QuerySpec) queryPart ).getSelectClause(); final int valuesArrayPosition = selectClause.getSqlSelections().size(); - final SqlSelection sqlSelection = expression.createSqlSelection( - valuesArrayPosition + 1, - valuesArrayPosition, - javaType, - typeConfiguration - ); + final SqlSelection sqlSelection; + if ( isTopLevel() ) { + sqlSelection = expression.createDomainResultSqlSelection( + valuesArrayPosition + 1, + valuesArrayPosition, + javaType, + typeConfiguration + ); + } + else { + sqlSelection = expression.createSqlSelection( + valuesArrayPosition + 1, + valuesArrayPosition, + javaType, + typeConfiguration + ); + } selectClause.addSqlSelection( sqlSelection ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 08503cb1ac71..5093bc92d6eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -1977,7 +1977,6 @@ protected void renderRecursiveCteVirtualSelections(SelectClause selectClause) { currentCteStatement.getCycleMarkColumn().getColumnExpression(), false, null, - null, currentCteStatement.getCycleMarkColumn().getJdbcMapping() ); if ( currentCteStatement.getCycleValue().getJdbcMapping() == getBooleanType() @@ -2020,7 +2019,6 @@ protected void emulateSearchClauseOrderWithRowAndArray(SelectClause selectClause depthColumnName, false, null, - null, integerType ); visitColumnReference( depthColumnReference ); @@ -2052,7 +2050,6 @@ protected void emulateSearchClauseOrderWithRowAndArray(SelectClause selectClause currentCteStatement.getSearchColumn().getColumnExpression(), false, null, - null, currentCteStatement.getSearchColumn().getJdbcMapping() ) ); @@ -2164,7 +2161,6 @@ private void emulateSearchClauseOrderWithString(SelectClause selectClause) { depthColumnName, false, null, - null, integerType ); visitColumnReference( depthColumnReference ); @@ -2208,7 +2204,6 @@ private void emulateSearchClauseOrderWithString(SelectClause selectClause) { currentCteStatement.getSearchColumn().getColumnExpression(), false, null, - null, currentCteStatement.getSearchColumn().getJdbcMapping() ) ); @@ -2353,7 +2348,6 @@ protected void emulateCycleClauseWithRowAndArray(SelectClause selectClause) { cyclePathColumnName, false, null, - null, stringType ); @@ -2500,7 +2494,6 @@ private void emulateCycleClauseWithString(SelectClause selectClause) { cyclePathColumnName, false, null, - null, stringType ); arguments.add( new QueryLiteral<>( "%", stringType ) ); @@ -2919,7 +2912,6 @@ protected void renderQueryGroup(QueryGroup queryGroup, boolean renderOrderByAndO "c" + i, false, null, - null, getIntegerType() ) ) @@ -3144,7 +3136,6 @@ else if ( expression instanceof SqmPathInterpretation ) { "c" + index, false, null, - null, expression.getExpressionType().getSingleJdbcMapping() ); } @@ -5608,7 +5599,6 @@ protected Predicate determineLateralEmulationPredicate(TableGroup tableGroup) { columnName, false, null, - null, null ) ); @@ -5678,7 +5668,6 @@ && supportsRowValueConstructorDistinctFromSyntax() ) { columnName, false, null, - null, null ) ); @@ -5803,7 +5792,6 @@ && supportsRowValueConstructorDistinctFromSyntax() ) { "sort_col_" + i, false, null, - null, null ); sortExpression = sortSpecification.getSortExpression(); @@ -6193,7 +6181,11 @@ public void visitParameter(JdbcParameter jdbcParameter) { break; case DEFAULT: default: - appendSql( PARAM_MARKER ); + jdbcParameter.getExpressionType() + .getJdbcMappings() + .get( 0 ) + .getJdbcType() + .appendWriteExpression( "?", this, getDialect() ); parameterBinders.add( jdbcParameter.getParameterBinder() ); jdbcParameters.addParameter( jdbcParameter ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstProcessingState.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstProcessingState.java index b07bb0bca05b..01b22f3a601f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstProcessingState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlAstProcessingState.java @@ -18,4 +18,8 @@ public interface SqlAstProcessingState { SqlExpressionResolver getSqlExpressionResolver(); SqlAstCreationState getSqlAstCreationState(); + + default boolean isTopLevel() {//todo: naming + return getParentState() == null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java index 48e1aed87ac0..94faeef4695f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/ColumnReference.java @@ -37,7 +37,6 @@ public class ColumnReference implements Expression, Assignable { private final SelectablePath selectablePath; private final boolean isFormula; private final String readExpression; - private final String customWriteExpression; private final JdbcMapping jdbcMapping; public ColumnReference(TableReference tableReference, SelectableMapping selectableMapping) { @@ -47,7 +46,6 @@ public ColumnReference(TableReference tableReference, SelectableMapping selectab selectableMapping.getSelectablePath(), selectableMapping.isFormula(), selectableMapping.getCustomReadExpression(), - selectableMapping.getCustomWriteExpression(), selectableMapping.getJdbcMapping() ); } @@ -59,7 +57,6 @@ public ColumnReference(TableReference tableReference, String mapping, JdbcMappin null, false, null, - null, jdbcMapping ); } @@ -71,7 +68,6 @@ public ColumnReference(String qualifier, SelectableMapping selectableMapping) { selectableMapping.getSelectablePath(), selectableMapping.isFormula(), selectableMapping.getCustomReadExpression(), - selectableMapping.getCustomWriteExpression(), selectableMapping.getJdbcMapping() ); } @@ -83,7 +79,6 @@ public ColumnReference(String qualifier, SelectableMapping selectableMapping, Jd selectableMapping.getSelectablePath(), selectableMapping.isFormula(), selectableMapping.getCustomReadExpression(), - selectableMapping.getCustomWriteExpression(), jdbcMapping ); } @@ -93,7 +88,6 @@ public ColumnReference( String columnExpression, boolean isFormula, String customReadExpression, - String customWriteExpression, JdbcMapping jdbcMapping) { this( tableReference.getIdentificationVariable(), @@ -101,7 +95,6 @@ public ColumnReference( null, isFormula, customReadExpression, - customWriteExpression, jdbcMapping ); } @@ -111,9 +104,8 @@ public ColumnReference( String columnExpression, boolean isFormula, String customReadExpression, - String customWriteExpression, JdbcMapping jdbcMapping) { - this( qualifier, columnExpression, null, isFormula, customReadExpression, customWriteExpression, jdbcMapping ); + this( qualifier, columnExpression, null, isFormula, customReadExpression, jdbcMapping ); } public ColumnReference( @@ -122,7 +114,6 @@ public ColumnReference( SelectablePath selectablePath, boolean isFormula, String customReadExpression, - String customWriteExpression, JdbcMapping jdbcMapping) { this.qualifier = StringHelper.nullIfEmpty( qualifier ); @@ -142,15 +133,6 @@ public ColumnReference( this.isFormula = isFormula; this.readExpression = customReadExpression; - - //TODO: writeExpression is never used, can it be removed? - if ( !isFormula && customWriteExpression != null ) { - this.customWriteExpression = customWriteExpression; - } - else { - this.customWriteExpression = null; - } - this.jdbcMapping = jdbcMapping; } @@ -175,10 +157,6 @@ public SelectablePath getSelectablePath() { return selectablePath; } - public String getCustomWriteExpression() { - return customWriteExpression; - } - public boolean isColumnExpressionFormula() { return isFormula; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Expression.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Expression.java index 21993ee72e3f..49768c096dcb 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Expression.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/Expression.java @@ -42,4 +42,23 @@ default SqlSelection createSqlSelection( this ); } + + default SqlSelection createDomainResultSqlSelection( + int jdbcPosition, + int valuesArrayPosition, + JavaType javaType, + TypeConfiguration typeConfiguration) { + // Apply possible jdbc type wrapping + final Expression expression; + final JdbcMappingContainer expressionType = getExpressionType(); + if ( expressionType == null ) { + expression = this; + } + else { + expression = expressionType.getJdbcMappings().get( 0 ).getJdbcType().wrapTopLevelSelectionExpression( this ); + } + return expression == this + ? createSqlSelection( jdbcPosition, valuesArrayPosition, javaType, typeConfiguration ) + : new SqlSelectionImpl( jdbcPosition, valuesArrayPosition, expression ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java index 31d931138a06..8816b14315d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JdbcType.java @@ -15,11 +15,13 @@ import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.Size; import org.hibernate.query.sqm.CastType; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.sql.ast.spi.StringBuilderSqlAppender; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.type.SqlTypes; import org.hibernate.type.descriptor.ValueBinder; import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; -import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; import org.hibernate.type.spi.TypeConfiguration; @@ -160,6 +162,35 @@ default String getCheckCondition(String columnName, JavaType javaType, Dialec return null; } + /** + * Wraps the top level selection expression to be able to read values with this JdbcType's ValueExtractor. + * @since 6.2 + */ + @Incubating + default Expression wrapTopLevelSelectionExpression(Expression expression) { + return expression; + } + + /** + * Wraps the write expression to be able to write values with this JdbcType's ValueBinder. + * @since 6.2 + */ + @Incubating + default String wrapWriteExpression(String writeExpression, Dialect dialect) { + final StringBuilder sb = new StringBuilder( writeExpression.length() ); + appendWriteExpression( writeExpression, new StringBuilderSqlAppender( sb ), dialect ); + return sb.toString(); + } + + /** + * Append the write expression wrapped in a way to be able to write values with this JdbcType's ValueBinder. + * @since 6.2 + */ + @Incubating + default void appendWriteExpression(String writeExpression, SqlAppender appender, Dialect dialect) { + appender.append( writeExpression ); + } + default boolean isInteger() { int typeCode = getDdlTypeCode(); return SqlTypes.isIntegral(typeCode) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonAsStringJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonAsStringJdbcType.java new file mode 100644 index 000000000000..e2faca2ea50e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/JsonAsStringJdbcType.java @@ -0,0 +1,170 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.type.descriptor.jdbc; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.spi.RuntimeModelCreationContext; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; + +/** + * Specialized type mapping for {@code JSON} and the JSON SQL data type. + * + * @author Christian Beikov + */ +public class JsonAsStringJdbcType extends JsonJdbcType implements AdjustableJdbcType { + /** + * Singleton access + */ + public static final JsonAsStringJdbcType VARCHAR_INSTANCE = new JsonAsStringJdbcType( SqlTypes.LONG32VARCHAR, null ); + public static final JsonAsStringJdbcType NVARCHAR_INSTANCE = new JsonAsStringJdbcType( SqlTypes.LONG32NVARCHAR, null ); + public static final JsonAsStringJdbcType CLOB_INSTANCE = new JsonAsStringJdbcType( SqlTypes.CLOB, null ); + public static final JsonAsStringJdbcType NCLOB_INSTANCE = new JsonAsStringJdbcType( SqlTypes.NCLOB, null ); + + private final boolean nationalized; + private final int ddlTypeCode; + protected JsonAsStringJdbcType(int ddlTypeCode, EmbeddableMappingType embeddableMappingType) { + super( embeddableMappingType ); + this.ddlTypeCode = ddlTypeCode; + this.nationalized = ddlTypeCode == SqlTypes.LONG32NVARCHAR + || ddlTypeCode == SqlTypes.NCLOB; + } + + @Override + public int getJdbcTypeCode() { + return nationalized ? SqlTypes.NVARCHAR : SqlTypes.VARCHAR; + } + + @Override + public int getDdlTypeCode() { + return ddlTypeCode; + } + + @Override + public String toString() { + return "JsonAsStringJdbcType"; + } + + @Override + public JdbcType resolveIndicatedType(JdbcTypeIndicators indicators, JavaType domainJtd) { + // Depending on the size of the column, we might have to adjust the jdbc type code for DDL. + // In some DBMS we can compare LOBs with special functions which is handled in the SqlAstTranslators, + // but that requires the correct jdbc type code to be available, which we ensure this way + if ( getEmbeddableMappingType() == null ) { + if ( needsLob( indicators ) ) { + return indicators.isNationalized() ? NCLOB_INSTANCE : CLOB_INSTANCE; + } + else { + return indicators.isNationalized() ? NVARCHAR_INSTANCE : VARCHAR_INSTANCE; + } + } + else { + if ( needsLob( indicators ) ) { + return new JsonAsStringJdbcType( + indicators.isNationalized() ? SqlTypes.NCLOB : SqlTypes.CLOB, + getEmbeddableMappingType() + ); + } + else { + return new JsonAsStringJdbcType( + indicators.isNationalized() ? SqlTypes.LONG32NVARCHAR : SqlTypes.LONG32VARCHAR, + getEmbeddableMappingType() + ); + } + } + } + + protected boolean needsLob(JdbcTypeIndicators indicators) { + final Dialect dialect = indicators.getTypeConfiguration() + .getServiceRegistry() + .getService( JdbcServices.class ) + .getDialect(); + final long length = indicators.getColumnLength(); + final long maxLength = indicators.isNationalized() ? + dialect.getMaxNVarcharLength() : + dialect.getMaxVarcharLength(); + if ( length > maxLength ) { + return true; + } + + final DdlTypeRegistry ddlTypeRegistry = indicators.getTypeConfiguration().getDdlTypeRegistry(); + final String typeName = ddlTypeRegistry.getTypeName( getDdlTypeCode(), dialect ); + return typeName.equals( ddlTypeRegistry.getTypeName( SqlTypes.CLOB, dialect ) ) + || typeName.equals( ddlTypeRegistry.getTypeName( SqlTypes.NCLOB, dialect ) ); + } + + @Override + public AggregateJdbcType resolveAggregateJdbcType( + EmbeddableMappingType mappingType, + String sqlType, + RuntimeModelCreationContext creationContext) { + return new JsonAsStringJdbcType( ddlTypeCode, mappingType ); + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + if ( nationalized ) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final String json = ( (JsonAsStringJdbcType) getJdbcType() ).toString( value, getJavaType(), options ); + st.setNString( index, json ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final String json = ( (JsonAsStringJdbcType) getJdbcType() ).toString( value, getJavaType(), options ); + st.setNString( name, json ); + } + }; + } + else { + return super.getBinder( javaType ); + } + } + + @Override + public ValueExtractor getExtractor(JavaType javaType) { + if ( nationalized ) { + return new BasicExtractor<>( javaType, this ) { + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return fromString( rs.getNString( paramIndex ), getJavaType(), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) + throws SQLException { + return fromString( statement.getNString( index ), getJavaType(), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + return fromString( statement.getNString( name ), getJavaType(), options ); + } + + }; + } + else { + return super.getExtractor( javaType ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/XmlAsStringJdbcType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/XmlAsStringJdbcType.java index eb17a98a0eaf..d37a80e2fa9c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/XmlAsStringJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/jdbc/XmlAsStringJdbcType.java @@ -11,6 +11,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.type.SqlTypes; @@ -18,20 +20,30 @@ import org.hibernate.type.descriptor.ValueExtractor; import org.hibernate.type.descriptor.WrapperOptions; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.sql.spi.DdlTypeRegistry; /** * Specialized type mapping for {@code SQLXML} and the XML SQL data type. * * @author Christian Beikov */ -public class XmlAsStringJdbcType extends XmlJdbcType { +public class XmlAsStringJdbcType extends XmlJdbcType implements AdjustableJdbcType { /** * Singleton access */ - public static final XmlAsStringJdbcType INSTANCE = new XmlAsStringJdbcType( null ); + public static final XmlAsStringJdbcType VARCHAR_INSTANCE = new XmlAsStringJdbcType( SqlTypes.LONG32VARCHAR, null ); + public static final XmlAsStringJdbcType NVARCHAR_INSTANCE = new XmlAsStringJdbcType( SqlTypes.LONG32NVARCHAR, null ); + public static final XmlAsStringJdbcType CLOB_INSTANCE = new XmlAsStringJdbcType( SqlTypes.CLOB, null ); + public static final XmlAsStringJdbcType NCLOB_INSTANCE = new XmlAsStringJdbcType( SqlTypes.NCLOB, null ); - private XmlAsStringJdbcType(EmbeddableMappingType embeddableMappingType) { + private final boolean nationalized; + private final int ddlTypeCode; + + public XmlAsStringJdbcType(int ddlTypeCode, EmbeddableMappingType embeddableMappingType) { super( embeddableMappingType ); + this.ddlTypeCode = ddlTypeCode; + this.nationalized = ddlTypeCode == SqlTypes.LONG32NVARCHAR + || ddlTypeCode == SqlTypes.NCLOB; } @Override @@ -39,12 +51,12 @@ public AggregateJdbcType resolveAggregateJdbcType( EmbeddableMappingType mappingType, String sqlType, RuntimeModelCreationContext creationContext) { - return new XmlAsStringJdbcType( mappingType ); + return new XmlAsStringJdbcType( ddlTypeCode, mappingType ); } @Override public int getJdbcTypeCode() { - return SqlTypes.VARCHAR; + return nationalized ? SqlTypes.NVARCHAR : SqlTypes.VARCHAR; } @Override @@ -52,68 +64,182 @@ public int getDefaultSqlTypeCode() { return SqlTypes.SQLXML; } + @Override + public int getDdlTypeCode() { + return ddlTypeCode; + } + @Override public String toString() { return "XmlAsStringJdbcType"; } @Override - public ValueBinder getBinder(JavaType javaType) { - return new BasicBinder<>( javaType, this ) { - @Override - protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) - throws SQLException { - final String xml = ( (XmlAsStringJdbcType) getJdbcType() ).toString( - value, - getJavaType(), - options + public JdbcType resolveIndicatedType(JdbcTypeIndicators indicators, JavaType domainJtd) { + // Depending on the size of the column, we might have to adjust the jdbc type code for DDL. + // In some DBMS we can compare LOBs with special functions which is handled in the SqlAstTranslators, + // but that requires the correct jdbc type code to be available, which we ensure this way + if ( getEmbeddableMappingType() == null ) { + if ( needsLob( indicators ) ) { + return indicators.isNationalized() ? NCLOB_INSTANCE : CLOB_INSTANCE; + } + else { + return indicators.isNationalized() ? NVARCHAR_INSTANCE : VARCHAR_INSTANCE; + } + } + else { + if ( needsLob( indicators ) ) { + return new XmlAsStringJdbcType( + indicators.isNationalized() ? SqlTypes.NCLOB : SqlTypes.CLOB, + getEmbeddableMappingType() ); - st.setString( index, xml ); } - - @Override - protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) - throws SQLException { - final String xml = ( (XmlAsStringJdbcType) getJdbcType() ).toString( - value, - getJavaType(), - options + else { + return new XmlAsStringJdbcType( + indicators.isNationalized() ? SqlTypes.LONG32NVARCHAR : SqlTypes.LONG32VARCHAR, + getEmbeddableMappingType() ); - st.setString( name, xml ); } - }; + } + } + + protected boolean needsLob(JdbcTypeIndicators indicators) { + final Dialect dialect = indicators.getTypeConfiguration() + .getServiceRegistry() + .getService( JdbcServices.class ) + .getDialect(); + final long length = indicators.getColumnLength(); + final long maxLength = indicators.isNationalized() ? + dialect.getMaxNVarcharLength() : + dialect.getMaxVarcharLength(); + if ( length > maxLength ) { + return true; + } + + final DdlTypeRegistry ddlTypeRegistry = indicators.getTypeConfiguration().getDdlTypeRegistry(); + final String typeName = ddlTypeRegistry.getTypeName( getDdlTypeCode(), dialect ); + return typeName.equals( ddlTypeRegistry.getTypeName( SqlTypes.CLOB, dialect ) ) + || typeName.equals( ddlTypeRegistry.getTypeName( SqlTypes.NCLOB, dialect ) ); + } + + @Override + public ValueBinder getBinder(JavaType javaType) { + if ( nationalized ) { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final String xml = ( (XmlAsStringJdbcType) getJdbcType() ).toString( + value, + getJavaType(), + options + ); + st.setNString( index, xml ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final String xml = ( (XmlAsStringJdbcType) getJdbcType() ).toString( + value, + getJavaType(), + options + ); + st.setNString( name, xml ); + } + }; + } + else { + return new BasicBinder<>( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + final String xml = ( (XmlAsStringJdbcType) getJdbcType() ).toString( + value, + getJavaType(), + options + ); + st.setString( index, xml ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + final String xml = ( (XmlAsStringJdbcType) getJdbcType() ).toString( + value, + getJavaType(), + options + ); + st.setString( name, xml ); + } + }; + } } @Override public ValueExtractor getExtractor(JavaType javaType) { - return new BasicExtractor<>( javaType, this ) { + if ( nationalized ) { + return new BasicExtractor<>( javaType, this ) { - @Override - protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { - return getObject( rs.getString( paramIndex ), options ); - } + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return getObject( rs.getNString( paramIndex ), options ); + } - @Override - protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { - return getObject( statement.getString( index ), options ); - } + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) + throws SQLException { + return getObject( statement.getNString( index ), options ); + } - @Override - protected X doExtract(CallableStatement statement, String name, WrapperOptions options) - throws SQLException { - return getObject( statement.getString( name ), options ); - } + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + return getObject( statement.getNString( name ), options ); + } - private X getObject(String xml, WrapperOptions options) throws SQLException { - if ( xml == null ) { - return null; + private X getObject(String xml, WrapperOptions options) throws SQLException { + if ( xml == null ) { + return null; + } + return ( (XmlAsStringJdbcType) getJdbcType() ).fromString( + xml, + getJavaType(), + options + ); } - return ( (XmlAsStringJdbcType) getJdbcType() ).fromString( - xml, - getJavaType(), - options - ); - } - }; + }; + } + else { + return new BasicExtractor<>( javaType, this ) { + + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return getObject( rs.getString( paramIndex ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return getObject( statement.getString( index ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + return getObject( statement.getString( name ), options ); + } + + private X getObject(String xml, WrapperOptions options) throws SQLException { + if ( xml == null ) { + return null; + } + return ( (XmlAsStringJdbcType) getJdbcType() ).fromString( + xml, + getJavaType(), + options + ); + } + }; + } } } diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/cockroachdb/CockroachDbContributor.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/cockroachdb/CockroachDbContributor.java index d797adbfb592..e10fff4c5760 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/cockroachdb/CockroachDbContributor.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/cockroachdb/CockroachDbContributor.java @@ -9,11 +9,14 @@ import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; +import org.hibernate.dialect.PostgreSQLPGObjectJdbcType; import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.FunctionKey; import org.hibernate.spatial.HSMessageLogger; import org.hibernate.spatial.contributor.ContributorImplementor; +import org.hibernate.spatial.dialect.postgis.PGCastingGeographyJdbcType; +import org.hibernate.spatial.dialect.postgis.PGCastingGeometryJdbcType; import org.hibernate.spatial.dialect.postgis.PGGeographyJdbcType; import org.hibernate.spatial.dialect.postgis.PGGeometryJdbcType; import org.hibernate.spatial.dialect.postgis.PostgisSqmFunctionDescriptors; @@ -29,8 +32,14 @@ public CockroachDbContributor(ServiceRegistry serviceRegistry) { @Override public void contributeJdbcTypes(TypeContributions typeContributions) { HSMessageLogger.SPATIAL_MSG_LOGGER.typeContributions( this.getClass().getCanonicalName() ); - typeContributions.contributeJdbcType( PGGeometryJdbcType.INSTANCE_WKB_2 ); - typeContributions.contributeJdbcType( PGGeographyJdbcType.INSTANCE_WKB_2 ); + if ( PostgreSQLPGObjectJdbcType.isUsable() ) { + typeContributions.contributeJdbcType( PGGeometryJdbcType.INSTANCE_WKB_2 ); + typeContributions.contributeJdbcType( PGGeographyJdbcType.INSTANCE_WKB_2 ); + } + else { + typeContributions.contributeJdbcType( PGCastingGeometryJdbcType.INSTANCE_WKB_2 ); + typeContributions.contributeJdbcType( PGCastingGeographyJdbcType.INSTANCE_WKB_2 ); + } } @Override diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/AbstractCastingPostGISJdbcType.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/AbstractCastingPostGISJdbcType.java new file mode 100644 index 000000000000..2701c55c78f6 --- /dev/null +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/AbstractCastingPostGISJdbcType.java @@ -0,0 +1,161 @@ +/* + * 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.spatial.dialect.postgis; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; + +import org.hibernate.dialect.Dialect; +import org.hibernate.spatial.GeometryLiteralFormatter; +import org.hibernate.sql.ast.spi.SqlAppender; +import org.hibernate.type.descriptor.ValueBinder; +import org.hibernate.type.descriptor.ValueExtractor; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.jdbc.BasicBinder; +import org.hibernate.type.descriptor.jdbc.BasicExtractor; +import org.hibernate.type.descriptor.jdbc.JdbcLiteralFormatter; +import org.hibernate.type.descriptor.jdbc.JdbcType; + +import org.geolatte.geom.ByteBuffer; +import org.geolatte.geom.Geometry; +import org.geolatte.geom.codec.Wkb; +import org.geolatte.geom.codec.WkbDecoder; +import org.geolatte.geom.codec.Wkt; +import org.geolatte.geom.codec.WktDecoder; +import org.geolatte.geom.codec.WktEncoder; + +/** + * Type Descriptor for the Postgis Geometry type + * + * @author Karel Maesen, Geovise BVBA + */ +public abstract class AbstractCastingPostGISJdbcType implements JdbcType { + + private final Wkb.Dialect wkbDialect; + + AbstractCastingPostGISJdbcType(Wkb.Dialect dialect) { + wkbDialect = dialect; + } + + @Override + public JdbcLiteralFormatter getJdbcLiteralFormatter(JavaType javaType) { + return new PGGeometryLiteralFormatter<>( getConstructorFunction(), javaType ); + } + + @Override + public abstract int getDefaultSqlTypeCode(); + + protected abstract String getConstructorFunction(); + + @Override + public void appendWriteExpression( + String writeExpression, + SqlAppender appender, + Dialect dialect) { + appender.append( getConstructorFunction() ); + appender.append( '(' ); + appender.append( writeExpression ); + appender.append( ')' ); + } + + public Geometry toGeometry(String wkt) { + if ( wkt == null ) { + return null; + } + if ( wkt.startsWith( "00" ) || wkt.startsWith( "01" ) ) { + //we have a WKB because this wkt starts with the bit-order byte + + ByteBuffer buffer = ByteBuffer.from( wkt ); + final WkbDecoder decoder = Wkb.newDecoder( wkbDialect ); + return decoder.decode( buffer ); + } + else { + return parseWkt( wkt ); + } + } + + private static Geometry parseWkt(String pgValue) { + final WktDecoder decoder = Wkt.newDecoder( Wkt.Dialect.POSTGIS_EWKT_1 ); + return decoder.decode( pgValue ); + } + + @Override + public int getJdbcTypeCode() { + return Types.VARCHAR; + } + + @Override + public ValueBinder getBinder(final JavaType javaType) { + return new BasicBinder( javaType, this ) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + st.setString( index, toWkt( value, options ) ); + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + st.setString( name, toWkt( value, options ) ); + } + + private String toWkt(X value, WrapperOptions options) throws SQLException { + final WktEncoder encoder = Wkt.newEncoder( Wkt.Dialect.POSTGIS_EWKT_1 ); + final Geometry geometry = getJavaType().unwrap( value, Geometry.class, options ); + return encoder.encode( geometry ); + } + + }; + } + + @Override + public ValueExtractor getExtractor(final JavaType javaType) { + return new BasicExtractor( javaType, this ) { + + + @Override + protected X doExtract(ResultSet rs, int paramIndex, WrapperOptions options) throws SQLException { + return getJavaType().wrap( toGeometry( rs.getString( paramIndex ) ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return getJavaType().wrap( toGeometry( statement.getString( index ) ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + return getJavaType().wrap( toGeometry( statement.getString( name ) ), options ); + } + }; + } + + static class PGGeometryLiteralFormatter extends GeometryLiteralFormatter { + + private final String constructorFunction; + + public PGGeometryLiteralFormatter(String constructorFunction, JavaType javaType) { + super( javaType, Wkt.Dialect.POSTGIS_EWKT_1, "" ); + this.constructorFunction = constructorFunction; + } + + @Override + public void appendJdbcLiteral(SqlAppender appender, T value, Dialect dialect, WrapperOptions wrapperOptions) { + Geometry geom = javaType.unwrap( value, Geometry.class, wrapperOptions ); + appender.append( constructorFunction ); + appender.appendSql( "('" ); + appender.appendSql( Wkt.toWkt( geom, wktDialect ) ); + appender.appendSql( "')" ); + } + } +} diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGCastingGeographyJdbcType.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGCastingGeographyJdbcType.java new file mode 100644 index 000000000000..a111ba3c47aa --- /dev/null +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGCastingGeographyJdbcType.java @@ -0,0 +1,38 @@ +/* + * 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.spatial.dialect.postgis; + +import org.hibernate.type.SqlTypes; + +import org.geolatte.geom.codec.Wkb; + +/** + * Type Descriptor for the Postgis Geography type + * + * @author Karel Maesen, Geovise BVBA + */ +public class PGCastingGeographyJdbcType extends AbstractCastingPostGISJdbcType { + + // Type descriptor instance using EWKB v2 (postgis versions >= 2.2.2, see: https://trac.osgeo.org/postgis/ticket/3181) + public static final PGCastingGeographyJdbcType INSTANCE_WKB_2 = new PGCastingGeographyJdbcType( Wkb.Dialect.POSTGIS_EWKB_2 ); + + private PGCastingGeographyJdbcType(Wkb.Dialect dialect) { + super( dialect ); + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.GEOGRAPHY; + } + + @Override + protected String getConstructorFunction() { + return "st_geogfromtext"; + } + +} diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGCastingGeometryJdbcType.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGCastingGeometryJdbcType.java new file mode 100644 index 000000000000..143b2ecb830f --- /dev/null +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PGCastingGeometryJdbcType.java @@ -0,0 +1,38 @@ +/* + * 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.spatial.dialect.postgis; + +import org.hibernate.type.SqlTypes; + +import org.geolatte.geom.codec.Wkb; + +/** + * Type Descriptor for the Postgis Geometry type + * + * @author Karel Maesen, Geovise BVBA + */ +public class PGCastingGeometryJdbcType extends AbstractCastingPostGISJdbcType { + + // Type descriptor instance using EWKB v2 (postgis versions >= 2.2.2, see: https://trac.osgeo.org/postgis/ticket/3181) + public static final PGCastingGeometryJdbcType INSTANCE_WKB_2 = new PGCastingGeometryJdbcType( Wkb.Dialect.POSTGIS_EWKB_2 ); + + private PGCastingGeometryJdbcType(Wkb.Dialect dialect) { + super( dialect ); + } + + @Override + public int getDefaultSqlTypeCode() { + return SqlTypes.GEOMETRY; + } + + @Override + protected String getConstructorFunction() { + return "st_geomfromewkt"; + } + +} diff --git a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisDialectContributor.java b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisDialectContributor.java index da4fcf446ffc..8479224ee8c3 100644 --- a/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisDialectContributor.java +++ b/hibernate-spatial/src/main/java/org/hibernate/spatial/dialect/postgis/PostgisDialectContributor.java @@ -9,6 +9,7 @@ import org.hibernate.boot.model.FunctionContributions; import org.hibernate.boot.model.TypeContributions; +import org.hibernate.dialect.PostgreSQLPGObjectJdbcType; import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.service.ServiceRegistry; import org.hibernate.spatial.HSMessageLogger; @@ -25,8 +26,14 @@ public PostgisDialectContributor(ServiceRegistry serviceRegistry) { @Override public void contributeJdbcTypes(TypeContributions typeContributions) { HSMessageLogger.SPATIAL_MSG_LOGGER.typeContributions( this.getClass().getCanonicalName() ); - typeContributions.contributeJdbcType( PGGeometryJdbcType.INSTANCE_WKB_2 ); - typeContributions.contributeJdbcType( PGGeographyJdbcType.INSTANCE_WKB_2 ); + if ( PostgreSQLPGObjectJdbcType.isUsable() ) { + typeContributions.contributeJdbcType( PGGeometryJdbcType.INSTANCE_WKB_2 ); + typeContributions.contributeJdbcType( PGGeographyJdbcType.INSTANCE_WKB_2 ); + } + else { + typeContributions.contributeJdbcType( PGCastingGeometryJdbcType.INSTANCE_WKB_2 ); + typeContributions.contributeJdbcType( PGCastingGeographyJdbcType.INSTANCE_WKB_2 ); + } } @Override From 606ae62499dec64f0b94c835ad6b2b7e07ac0dd9 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 27 Jan 2023 17:25:22 -0600 Subject: [PATCH 0008/1497] Unify example includes in terms of well-defined asciidoc attributes preparation step for moving tests out of `documentation` and into the proper projects (core, envers and spatial) --- .../userguide/Hibernate_User_Guide.adoc | 2 + .../userguide/chapters/batch/Batching.adoc | 35 +-- .../userguide/chapters/beans/Beans.adoc | 20 +- .../chapters/bootstrap/Bootstrap.adoc | 35 +-- .../userguide/chapters/caching/Caching.adoc | 46 ++-- .../userguide/chapters/domain/access.adoc | 16 +- .../chapters/domain/associations.adoc | 86 +++---- .../chapters/domain/basic_types.adoc | 232 +++++++++--------- .../chapters/domain/collections.adoc | 114 ++++----- .../chapters/domain/customizing.adoc | 12 +- .../chapters/domain/dynamic_model.adoc | 14 +- .../chapters/domain/embeddables.adoc | 62 ++--- .../userguide/chapters/domain/entity.adoc | 58 ++--- .../chapters/domain/identifiers.adoc | 71 +++--- .../chapters/domain/immutability.adoc | 20 +- .../chapters/domain/inheritance.adoc | 38 +-- .../userguide/chapters/domain/naming.adoc | 6 +- .../userguide/chapters/domain/natural_id.adoc | 28 ++- .../userguide/chapters/domain/types.adoc | 6 +- .../userguide/chapters/envers/Envers.adoc | 102 ++++---- .../userguide/chapters/events/Events.adoc | 32 +-- .../userguide/chapters/fetching/Fetching.adoc | 62 ++--- .../userguide/chapters/flushing/Flushing.adoc | 28 ++- .../userguide/chapters/locking/Locking.adoc | 40 +-- .../chapters/multitenancy/MultiTenancy.adoc | 24 +- .../chapters/pc/BytecodeEnhancement.adoc | 12 +- .../chapters/pc/PersistenceContext.adoc | 158 ++++++------ .../chapters/query/criteria/Criteria.adoc | 30 +-- .../userguide/chapters/query/hql/Query.adoc | 82 ++++--- .../chapters/query/hql/QueryLanguage.adoc | 196 +++++++-------- .../chapters/query/native/Native.adoc | 136 +++++----- .../chapters/query/spatial/Spatial.adoc | 10 +- .../userguide/chapters/schema/Schema.adoc | 26 +- .../chapters/statistics/Statistics.adoc | 1 - .../userguide/chapters/tooling/Tooling.adoc | 6 +- .../userguide/chapters/tooling/modelgen.adoc | 18 +- .../chapters/transactions/Transactions.adoc | 10 +- 37 files changed, 967 insertions(+), 907 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc b/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc index a781d8199fee..41158ceb785d 100644 --- a/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc +++ b/documentation/src/main/asciidoc/userguide/Hibernate_User_Guide.adoc @@ -3,6 +3,8 @@ Vlad Mihalcea, Steve Ebersole, Andrea Boriero, Gunnar Morling, Gail Badner, Chri :toc2: :toclevels: 3 :sectanchors: +:root-project-dir: ../../../../../../.. + include::Preface.adoc[] diff --git a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc index 9a65d6e19fc5..d6c80a513e7b 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/batch/Batching.adoc @@ -1,7 +1,10 @@ [[batch]] == Batching -:sourcedir: ../../../../../test/java/org/hibernate/userguide/batch -:bulkid-sourcedir: ../../../../../../../hibernate-core/src/test/java/org/hibernate/orm/test/bulkid +:root-project-dir: ../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:core-project-dir: {root-project-dir}/hibernate-core +:example-dir-doc-batch: {documentation-project-dir}/src/test/java/org/hibernate/userguide/batch +:example-dir-bulkid: {core-project-dir}/src/test/java/org/hibernate/orm/test/bulkid :extrasdir: extras [[batch-jdbcbatch]] @@ -46,7 +49,7 @@ Since version 5.2, Hibernate allows overriding the global JDBC batch size given ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-session-jdbc-batch-size-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-session-jdbc-batch-size-example] ---- ==== @@ -60,7 +63,7 @@ The following example shows an anti-pattern for batch inserts. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-session-batch-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-session-batch-example] ---- ==== @@ -88,7 +91,7 @@ When you make new objects persistent, employ methods `flush()` and `clear()` to ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-session-batch-insert-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-session-batch-insert-example] ---- ==== @@ -103,7 +106,7 @@ In addition, use method `scroll()` to take advantage of server-side cursors for ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-session-scroll-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-session-scroll-example] ---- ==== @@ -149,7 +152,7 @@ IMPORTANT: Due to the lack of a first-level cache, stateless sessions are vulner ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-stateless-session-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-stateless-session-example] ---- ==== @@ -205,7 +208,7 @@ You can use sub-queries in the `WHERE` clause, and the sub-queries themselves ca ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-bulk-jpql-update-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-bulk-jpql-update-example] ---- ==== @@ -214,7 +217,7 @@ include::{sourcedir}/BatchTest.java[tags=batch-bulk-jpql-update-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-bulk-hql-update-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-bulk-hql-update-example] ---- ==== @@ -226,7 +229,7 @@ You can use a versioned update to force Hibernate to reset the version or timest ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-bulk-hql-update-version-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-bulk-hql-update-version-example] ---- ==== @@ -242,7 +245,7 @@ This feature is only available in HQL since it's not standardized by Jakarta Per ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-bulk-jpql-delete-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-bulk-jpql-delete-example] ---- ==== @@ -251,7 +254,7 @@ include::{sourcedir}/BatchTest.java[tags=batch-bulk-jpql-delete-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-bulk-hql-delete-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-bulk-hql-delete-example] ---- ==== @@ -309,7 +312,7 @@ in which case the seed value defined by the `org.hibernate.type.descriptor.java. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BatchTest.java[tags=batch-bulk-hql-insert-example] +include::{example-dir-doc-batch}/BatchTest.java[tags=batch-bulk-hql-insert-example] ---- ==== @@ -344,7 +347,7 @@ The `Person` entity is the base class of this entity inheritance model, and is m ==== [source, JAVA, indent=0] ---- -include::{bulkid-sourcedir}/AbstractMutationStrategyCompositeIdTest.java[tags=batch-bulk-hql-temp-table-base-class-example] +include::{example-dir-bulkid}/AbstractMutationStrategyCompositeIdTest.java[tags=batch-bulk-hql-temp-table-base-class-example] ---- ==== @@ -355,7 +358,7 @@ Both the `Doctor` and `Engineer` entity classes extend the `Person` base class: ==== [source, JAVA, indent=0] ---- -include::{bulkid-sourcedir}/AbstractMutationStrategyIdTest.java[tags=batch-bulk-hql-temp-table-sub-classes-example] +include::{example-dir-bulkid}/AbstractMutationStrategyIdTest.java[tags=batch-bulk-hql-temp-table-sub-classes-example] ---- ==== @@ -369,7 +372,7 @@ Now, when you try to execute a bulk entity delete query: ==== [source, JAVA, indent=0] ---- -include::{bulkid-sourcedir}/AbstractMutationStrategyCompositeIdTest.java[tags=batch-bulk-hql-temp-table-delete-query-example] +include::{example-dir-bulkid}/AbstractMutationStrategyCompositeIdTest.java[tags=batch-bulk-hql-temp-table-delete-query-example] ---- [source, SQL, indent=0] diff --git a/documentation/src/main/asciidoc/userguide/chapters/beans/Beans.adoc b/documentation/src/main/asciidoc/userguide/chapters/beans/Beans.adoc index 411b86910a49..8574ce72f08f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/beans/Beans.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/beans/Beans.adoc @@ -1,12 +1,6 @@ [[beans]] == Managed Beans -:rootProjectDir: ../../../../../../.. -:sourcedir: ../../../../../test/java/org/hibernate/userguide/beans -:coreProjectDir: {rootProjectDir}/hibernate-core -:coreTestSrcDir: {rootProjectDir}/hibernate-core/src/test/java -:instantiatorTestDir: {coreTestSrcDir}/org/hibernate/orm/test/mapping/embeddable/strategy/instantiator :extrasdir: extras -:fn-cdi-availability: footnote:disclaimer[With delayed or extended CDI availability, IdentifierGenerators cannot be resolved from CDI due to timing. See <>] Hibernate supports consuming many of its extension points as "managed beans". A bean being managed simply means that its creation and lifecycle are managed by a container of some sort. @@ -25,10 +19,11 @@ the SessionFactory. It supports a number of ways to influence how this process [[beans-manageable]] === Manageable Beans -Hibernate supports using the following integrations as managed beans: +Jakarta Persistence defines support for resolving `AttributeConverter` and +"entity listener" classes as managed beans. + +Additionally, Hibernate supports resolving the following integrations as managed beans: -* `jakarta.persistence.AttributeConverter` -* Jakarta Persistence "entity listener" classes * `org.hibernate.type.descriptor.jdbc.JdbcType` * `org.hibernate.type.descriptor.java.BasicJavaType` * `org.hibernate.type.descriptor.java.MutabilityPlan` @@ -36,7 +31,10 @@ Hibernate supports using the following integrations as managed beans: * `org.hibernate.usertype.UserCollectionType` * `org.hibernate.metamodel.EmbeddableInstantiator` * `org.hibernate.envers.RevisionListener` -* `org.hibernate.id.IdentifierGenerator`{fn-cdi-availability} +* `org.hibernate.id.IdentifierGenerator` + +NOTE: At the moment, when using either <> or <> +CDI access, resolving these Hibernate integrations as managed beans is disabled. [[beans-cdi]] @@ -94,4 +92,4 @@ NOTE: When used in WildFly, this is all automatically set up by the server === Custom BeanContainer Other containers (Spring, e.g.) can also be used and integrated by implementing `BeanContainer` and -declaring it using `hibernate.resource.beans.container`. \ No newline at end of file +declaring it using `hibernate.resource.beans.container`. diff --git a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc index df84daa08dd0..2bb2152299b9 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/bootstrap/Bootstrap.adoc @@ -1,7 +1,10 @@ [[bootstrap]] == Bootstrap -:sourcedir: ../../../../../test/java/org/hibernate/userguide/bootstrap -:boot-spi-sourcedir: ../../../../../../../hibernate-core/src/test/java/org/hibernate/orm/test/bootstrap/spi +:root-project-dir: ../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:core-project-dir: {root-project-dir}/hibernate-core +:example-dir-boot: {documentation-project-dir}/src/test/java/org/hibernate/userguide/bootstrap +:example-dir-boot-spi: {core-project-dir}/src/test/java/org/hibernate/orm/test/bootstrap/spi :extrasdir: extras The term bootstrapping refers to initializing and starting a software component. @@ -48,7 +51,7 @@ If you wish to alter how the `BootstrapServiceRegistry` is built, that is contro ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-bootstrap-native-registry-BootstrapServiceRegistry-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-bootstrap-native-registry-BootstrapServiceRegistry-example] ---- ==== @@ -65,7 +68,7 @@ You will almost always need to configure the `StandardServiceRegistry`, which is ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-bootstrap-native-registry-StandardServiceRegistryBuilder-example] ---- ==== @@ -79,7 +82,7 @@ Some specific methods of interest: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-bootstrap-native-registry-MetadataSources-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-bootstrap-native-registry-MetadataSources-example] ---- ==== @@ -93,7 +96,7 @@ The main use cases for an `org.hibernate.integrator.spi.Integrator` right now ar ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-event-listener-registration-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-event-listener-registration-example] ---- ==== @@ -112,7 +115,7 @@ Also, all methods on `MetadataSources` offer fluent-style call chaining:: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-native-metadata-source-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-native-metadata-source-example] ---- ==== @@ -136,7 +139,7 @@ See its https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hi ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-native-metadata-builder-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-native-metadata-builder-example] ---- ==== @@ -153,7 +156,7 @@ However, if you would like to adjust that building process, you will need to use ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-native-SessionFactory-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-native-SessionFactory-example] ---- ==== @@ -168,7 +171,7 @@ The bootstrapping API is quite flexible, but in most cases it makes the most sen ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-native-SessionFactoryBuilder-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-native-SessionFactoryBuilder-example] ---- ==== @@ -196,7 +199,7 @@ and make that available to the application for injection via the `jakarta.persis ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceUnit-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceUnit-example] ---- ==== @@ -208,7 +211,7 @@ you can inject a specific `EntityManagerFactory` by Unit name: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceUnit-configurable-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceUnit-configurable-example] ---- ==== @@ -231,7 +234,7 @@ The application creates an `EntityManagerFactory` by calling the `createEntityMa ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-EntityManagerFactory-example] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-jpa-compliant-EntityManagerFactory-example] ---- ==== @@ -249,7 +252,7 @@ To inject the default Persistence Context, you can use the {jpaJavadocUrlPrefix} ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceContext-example, indent=0] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceContext-example, indent=0] ---- ==== @@ -264,7 +267,7 @@ and you can even pass `EntityManager`-specific properties using the ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceContext-configurable-example, indent=0] +include::{example-dir-boot}/BootstrapTest.java[tags=bootstrap-jpa-compliant-PersistenceContext-configurable-example, indent=0] ---- ==== @@ -324,7 +327,7 @@ https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/ ==== [source, JAVA, indent=0] ---- -include::{boot-spi-sourcedir}/metadatabuildercontributor/SqlFunctionMetadataBuilderContributor.java[tags=bootstrap-jpa-compliant-MetadataBuilderContributor-example] +include::{example-dir-boot-spi}/metadatabuildercontributor/SqlFunctionMetadataBuilderContributor.java[tags=bootstrap-jpa-compliant-MetadataBuilderContributor-example] ---- ==== org.hibernate.orm.test.bootstrap.spi.metadatabuildercontributor diff --git a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc index 4904cb220ede..cc3504d992fc 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/caching/Caching.adoc @@ -1,6 +1,8 @@ [[caching]] == Caching -:sourcedir: ../../../../../test/java/org/hibernate/userguide/caching +:root-project-dir: ../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:example-dir-caching: {documentation-project-dir}/src/test/java/org/hibernate/userguide/caching At runtime, Hibernate handles moving data into and out of the second-level cache in response to the operations performed by the `Session`, which acts as a transaction-level cache of persistent data. Once an entity becomes managed, that object is added to the internal cache of the current persistence context (`EntityManager` or `Session`). @@ -167,7 +169,7 @@ Nevertheless, the reasons why we advise you to have all entities belonging to an ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/NonStrictReadWriteCacheTest.java[tags=caching-entity-mapping-example] +include::{example-dir-caching}/NonStrictReadWriteCacheTest.java[tags=caching-entity-mapping-example] ---- ==== @@ -182,7 +184,7 @@ Once an entity is stored in the second-level cache, you can avoid a database hit ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-entity-jpa-example] +include::{example-dir-caching}/SecondLevelCacheTest.java[tags=caching-entity-jpa-example] ---- ==== @@ -191,7 +193,7 @@ include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-entity-jpa-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/SecondLevelCacheTest.java[tags=caching-entity-native-example] +include::{example-dir-caching}/SecondLevelCacheTest.java[tags=caching-entity-native-example] ---- ==== @@ -202,7 +204,7 @@ The Hibernate second-level cache can also load entities by their <> section. +<> section. If `@JdbcTypeCode` is used, the Dialect is still consulted to make sure the database supports the requested type. If not, an appropriate type is selected @@ -566,7 +568,7 @@ By default, Hibernate maps values of `Float` to the `FLOAT`, `REAL` or ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/FloatMappingTests.java[tags=basic-float-example-implicit] +include::{example-dir-basic-mapping}/basic/FloatMappingTests.java[tags=basic-float-example-implicit] ---- ==== @@ -590,7 +592,7 @@ By default, Hibernate maps values of `BigDecimal` to the `NUMERIC` JDBC type. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/BigDecimalMappingTests.java[tags=basic-bigdecimal-example-implicit] +include::{example-dir-basic-mapping}/basic/BigDecimalMappingTests.java[tags=basic-bigdecimal-example-implicit] ---- ==== @@ -608,7 +610,7 @@ By default, Hibernate maps `Character` to the `CHAR` JDBC type. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/CharacterMappingTests.java[tags=basic-character-example-implicit] +include::{example-dir-basic-mapping}/basic/CharacterMappingTests.java[tags=basic-character-example-implicit] ---- ==== @@ -626,7 +628,7 @@ By default, Hibernate maps `String` to the `VARCHAR` JDBC type. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/StringMappingTests.java[tags=basic-string-example] +include::{example-dir-basic-mapping}/basic/StringMappingTests.java[tags=basic-string-example] ---- ==== @@ -673,7 +675,7 @@ nationalized data. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/StringNationalizedMappingTests.java[tags=basic-nstring-example] +include::{example-dir-basic-mapping}/basic/StringNationalizedMappingTests.java[tags=basic-nstring-example] ---- ==== @@ -694,7 +696,7 @@ By default, Hibernate maps `Character[]` and `char[]` to the `VARCHAR` JDBC type ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/CharacterArrayMappingTests.java[tags=basic-chararray-example] +include::{example-dir-basic-mapping}/basic/CharacterArrayMappingTests.java[tags=basic-chararray-example] ---- ==== @@ -708,7 +710,7 @@ nationalized data. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/CharacterArrayNationalizedMappingTests.java[tags=basic-nchararray-example] +include::{example-dir-basic-mapping}/basic/CharacterArrayNationalizedMappingTests.java[tags=basic-nchararray-example] ---- ==== @@ -747,7 +749,7 @@ Let's first map this using the `@Lob` Jakarta Persistence annotation and the `ja ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ClobTest.java[tags=basic-clob-example] +include::{example-dir-basic-mapping}/basic/ClobTest.java[tags=basic-clob-example] ---- ==== @@ -758,7 +760,7 @@ To persist such an entity, you have to create a `Clob` using the `ClobProxy` Hib ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ClobTest.java[tags=basic-clob-persist-example] +include::{example-dir-basic-mapping}/basic/ClobTest.java[tags=basic-clob-persist-example] ---- ==== @@ -769,7 +771,7 @@ To retrieve the `Clob` content, you need to transform the underlying `java.io.Re ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ClobTest.java[tags=basic-clob-find-example] +include::{example-dir-basic-mapping}/basic/ClobTest.java[tags=basic-clob-find-example] ---- ==== @@ -780,7 +782,7 @@ We could also map the CLOB in a materialized form. This way, we can either use a ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ClobStringTest.java[tags=basic-clob-string-example] +include::{example-dir-basic-mapping}/basic/ClobStringTest.java[tags=basic-clob-string-example] ---- ==== @@ -791,7 +793,7 @@ We might even want the materialized data as a char array. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ClobCharArrayTest.java[tags=basic-clob-char-array-example] +include::{example-dir-basic-mapping}/basic/ClobCharArrayTest.java[tags=basic-clob-char-array-example] ---- ==== @@ -813,7 +815,7 @@ Hibernate can map the `NCLOB` to a `java.sql.NClob` ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/NClobTest.java[tags=basic-nclob-example] +include::{example-dir-basic-mapping}/basic/NClobTest.java[tags=basic-nclob-example] ---- ==== @@ -824,7 +826,7 @@ To persist such an entity, you have to create an `NClob` using the `NClobProxy` ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/NClobTest.java[tags=basic-nclob-persist-example] +include::{example-dir-basic-mapping}/basic/NClobTest.java[tags=basic-nclob-persist-example] ---- ==== @@ -835,7 +837,7 @@ To retrieve the `NClob` content, you need to transform the underlying `java.io.R ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/NClobTest.java[tags=basic-nclob-find-example] +include::{example-dir-basic-mapping}/basic/NClobTest.java[tags=basic-nclob-find-example] ---- ==== @@ -846,7 +848,7 @@ We could also map the `NCLOB` in a materialized form. This way, we can either us ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/NClobStringTest.java[tags=basic-nclob-string-example] +include::{example-dir-basic-mapping}/basic/NClobStringTest.java[tags=basic-nclob-string-example] ---- ==== @@ -857,7 +859,7 @@ We might even want the materialized data as a char array. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/NClobCharArrayTest.java[tags=basic-nclob-char-array-example] +include::{example-dir-basic-mapping}/basic/NClobCharArrayTest.java[tags=basic-nclob-char-array-example] ---- ==== @@ -878,7 +880,7 @@ By default, Hibernate maps values of type `byte[]` and `Byte[]` to the JDBC type ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ByteArrayMappingTests.java[tags=basic-bytearray-example] +include::{example-dir-basic-mapping}/basic/ByteArrayMappingTests.java[tags=basic-bytearray-example] ---- ==== @@ -936,7 +938,7 @@ Let's first map this using the JDBC `java.sql.Blob` type. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/BlobTest.java[tags=basic-blob-example] +include::{example-dir-basic-mapping}/basic/BlobTest.java[tags=basic-blob-example] ---- ==== @@ -947,7 +949,7 @@ To persist such an entity, you have to create a `Blob` using the `BlobProxy` Hib ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/BlobTest.java[tags=basic-blob-persist-example] +include::{example-dir-basic-mapping}/basic/BlobTest.java[tags=basic-blob-persist-example] ---- ==== @@ -958,7 +960,7 @@ To retrieve the `Blob` content, you need to transform the underlying `java.io.In ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/BlobTest.java[tags=basic-blob-find-example] +include::{example-dir-basic-mapping}/basic/BlobTest.java[tags=basic-blob-find-example] ---- ==== @@ -969,7 +971,7 @@ We could also map the BLOB in a materialized form (e.g. `byte[]`). ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/BlobByteArrayTest.java[tags=basic-blob-byte-array-example] +include::{example-dir-basic-mapping}/basic/BlobByteArrayTest.java[tags=basic-blob-byte-array-example] ---- ==== @@ -989,7 +991,7 @@ TIP: It's possible to map `Duration` to the `INTERVAL_SECOND` SQL type using `@J ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/DurationMappingTests.java[tags=basic-duration-example] +include::{example-dir-basic-mapping}/basic/DurationMappingTests.java[tags=basic-duration-example] ---- ==== @@ -1007,7 +1009,7 @@ include::{sourcedir}/basic/DurationMappingTests.java[tags=basic-duration-example ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/InstantMappingTests.java[tags=basic-instant-example] +include::{example-dir-basic-mapping}/basic/InstantMappingTests.java[tags=basic-instant-example] ---- ==== @@ -1028,7 +1030,7 @@ See <> for basics of temporal mapping ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/LocalDateMappingTests.java[tags=basic-localDate-example] +include::{example-dir-basic-mapping}/basic/LocalDateMappingTests.java[tags=basic-localDate-example] ---- ==== @@ -1049,7 +1051,7 @@ See <> for basics of temporal mapping ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/LocalDateTimeMappingTests.java[tags=basic-localDateTime-example] +include::{example-dir-basic-mapping}/basic/LocalDateTimeMappingTests.java[tags=basic-localDateTime-example] ---- ==== @@ -1070,7 +1072,7 @@ See <> for basics of temporal mapping ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/LocalTimeMappingTests.java[tags=basic-localTime-example] +include::{example-dir-basic-mapping}/basic/LocalTimeMappingTests.java[tags=basic-localTime-example] ---- ==== @@ -1093,7 +1095,7 @@ depending on the database. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/OffsetDateTimeMappingTests.java[tags=basic-OffsetDateTime-example] +include::{example-dir-basic-mapping}/basic/OffsetDateTimeMappingTests.java[tags=basic-OffsetDateTime-example] ---- ==== @@ -1119,7 +1121,7 @@ depending on the database. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/OffsetTimeMappingTests.java[tags=basic-offsetTime-example] +include::{example-dir-basic-mapping}/basic/OffsetTimeMappingTests.java[tags=basic-offsetTime-example] ---- ==== @@ -1141,7 +1143,7 @@ See <> for basics of time-zone handling ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/TimeZoneMappingTests.java[tags=basic-timeZone-example] +include::{example-dir-basic-mapping}/basic/TimeZoneMappingTests.java[tags=basic-timeZone-example] ---- ==== @@ -1163,7 +1165,7 @@ depending on the database. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ZonedDateTimeMappingTests.java[tags=basic-ZonedDateTime-example] +include::{example-dir-basic-mapping}/basic/ZonedDateTimeMappingTests.java[tags=basic-ZonedDateTime-example] ---- ==== @@ -1187,7 +1189,7 @@ See <> for basics of time-zone handling ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ZoneOffsetMappingTests.java[tags=basic-ZoneOffset-example] +include::{example-dir-basic-mapping}/basic/ZoneOffsetMappingTests.java[tags=basic-ZoneOffset-example] ---- ==== @@ -1257,7 +1259,7 @@ Hibernate maps `Class` references to `VARCHAR` JDBC type ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ClassMappingTests.java[tags=basic-Class-example] +include::{example-dir-basic-mapping}/basic/ClassMappingTests.java[tags=basic-Class-example] ---- ==== @@ -1272,7 +1274,7 @@ Hibernate maps `Currency` references to `VARCHAR` JDBC type ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/CurrencyMappingTests.java[tags=basic-Currency-example] +include::{example-dir-basic-mapping}/basic/CurrencyMappingTests.java[tags=basic-Currency-example] ---- ==== @@ -1287,7 +1289,7 @@ Hibernate maps `Locale` references to `VARCHAR` JDBC type ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/LocaleMappingTests.java[tags=basic-Locale-example] +include::{example-dir-basic-mapping}/basic/LocaleMappingTests.java[tags=basic-Locale-example] ---- ==== @@ -1338,7 +1340,7 @@ By default, Hibernate will map `InetAddress` to the `INET` SQL type and fallback ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/InetAddressMappingTests.java[tags=basic-inet-address-example] +include::{example-dir-basic-mapping}/basic/InetAddressMappingTests.java[tags=basic-inet-address-example] ---- ==== @@ -1355,7 +1357,7 @@ as can be read in the <> with two differences: +This functionality is similar to a derived-property <> with two differences: * The property is backed by one or more columns that are exported as part of automatic schema generation. * The property is read-write, not read-only. @@ -2641,7 +2643,7 @@ The `write` expression, if specified, must contain exactly one '?' placeholder f ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/basic/ColumnTransformerTest.java[tags=mapping-column-read-and-write-composite-type-persistence-example] +include::{example-dir-basic-mapping}/basic/ColumnTransformerTest.java[tags=mapping-column-read-and-write-composite-type-persistence-example] ---- [source, SQL, indent=0] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc index 325010be2bf6..597d3933bf36 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/collections.adoc @@ -1,16 +1,16 @@ [[collections]] === Collections -:rootProjectDir: ../../../../../../.. -:documentationProjectDir: {rootProjectDir}/documentation -:docTestsDir: ../../../../../test/java/org/hibernate/userguide/collections -:coreProjectDir: {rootProjectDir}/hibernate-core -:coreTestsDir: {coreProjectDir}/src/test/java -:coreCollectionTestsDir: {coreTestsDir}/org/hibernate/orm/test/mapping/collections -:classificationTestsDir: {coreCollectionTestsDir}/classification -:extrasdir: extras/collections -:docs-base: https://docs.jboss.org/hibernate/orm/6.0 +:majorMinorVersion: 6.2 +:root-project-dir: ../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:example-dir-collection-doc: {documentation-project-dir}/src/test/java/org/hibernate/userguide/collections +:core-project-dir: {root-project-dir}/hibernate-core +:core-test-base: {core-project-dir}/src/test/java +:example-dir-collection: {core-test-base}/org/hibernate/orm/test/mapping/collections +:docs-base: https://docs.jboss.org/hibernate/orm/{majorMinorVersion} :javadoc-base: {docs-base}/javadoc :java-javadoc-base: https://docs.oracle.com/en/java/javase/11/docs/api/java.base +:extrasdir: extras/collections Hibernate supports mapping collections (`java.util.Collection` and `java.util.Map` subtypes) in a variety of ways. @@ -80,7 +80,7 @@ interpretation as to which classification it fits in to, using the following che ==== [source, JAVA, indent=0] ---- -include::{classificationTestsDir}/list/EntityWithList.java[tags=collections-list-ex] +include::{example-dir-collection}/classification/list/EntityWithList.java[tags=collections-list-ex] ---- ==== @@ -108,7 +108,7 @@ The default column name that stores the index is derived from the attribute name ==== [source, JAVA, indent=0] ---- -include::{classificationTestsDir}/list/EntityWithOrderColumnList.java[tags=collections-list-ordercolumn-ex] +include::{example-dir-collection}/classification/list/EntityWithOrderColumnList.java[tags=collections-list-ordercolumn-ex] ---- ==== @@ -125,7 +125,7 @@ cases using its `@ListIndexBase` annotation. ==== [source, JAVA, indent=0] ---- -include::{classificationTestsDir}/list/EntityWithIndexBasedList.java[tags=collections-list-indexbase-ex] +include::{example-dir-collection}/classification/list/EntityWithIndexBasedList.java[tags=collections-list-indexbase-ex] ---- ==== @@ -145,7 +145,7 @@ mapping sets according to the requirements of the `java.util.Set`. ==== [source, JAVA, indent=0] ---- -include::{classificationTestsDir}/set/EntityWithSet.java[tags=collections-set-ex] +include::{example-dir-collection}/classification/set/EntityWithSet.java[tags=collections-set-ex] ---- ==== @@ -172,9 +172,9 @@ this implies that the element type is `Comparable`. E.g. ==== [source, JAVA, indent=0] ---- -include::{classificationTestsDir}/Name.java[tags=collections-name-ex] +include::{example-dir-collection}/classification/Name.java[tags=collections-name-ex] -include::{classificationTestsDir}/set/EntityWithNaturallySortedSet.java[tags=collections-sortedset-natural-ex] +include::{example-dir-collection}/classification/set/EntityWithNaturallySortedSet.java[tags=collections-sortedset-natural-ex] ---- ==== @@ -189,9 +189,9 @@ the `Names` as sorted by a `NameComparator`: ==== [source, JAVA, indent=0] ---- -include::{classificationTestsDir}/NameComparator.java[tags=collections-name-comparator-ex] +include::{example-dir-collection}/classification/NameComparator.java[tags=collections-name-comparator-ex] -include::{classificationTestsDir}/set/EntityWithSortedSet.java[tags=collections-sortedset-comparator-ex] +include::{example-dir-collection}/classification/set/EntityWithSortedSet.java[tags=collections-sortedset-comparator-ex] ---- ==== @@ -232,7 +232,7 @@ are handled is largely undefined. ==== [source, JAVA, indent=0] ---- -include::{classificationTestsDir}/bag/EntityWithBagAsCollection.java[tags=collections-bag-ex] +include::{example-dir-collection}/classification/bag/EntityWithBagAsCollection.java[tags=collections-bag-ex] ---- ==== @@ -244,7 +244,7 @@ lists as bags. First an explicit annotation ==== [source, JAVA, indent=0] ---- -include::{classificationTestsDir}/bag/EntityWithBagAsList.java[tags=collections-bag-list-ex] +include::{example-dir-collection}/classification/bag/EntityWithBagAsList.java[tags=collections-bag-list-ex] ---- ==== @@ -302,7 +302,7 @@ The embeddable used in the examples is a `PhoneNumber` - ==== [source,java] ---- -include::{coreCollectionTestsDir}/nature/elemental/Phone.java[tags=ex-collection-elemental-model,indent=0] +include::{example-dir-collection}/nature/elemental/Phone.java[tags=ex-collection-elemental-model,indent=0] ---- ==== @@ -314,7 +314,7 @@ First, a BAG mapping - ==== [source,java] ---- -include::{coreCollectionTestsDir}/nature/elemental/ElementalBagTest.java[tags=ex-collection-elemental-model,indent=0] +include::{example-dir-collection}/nature/elemental/ElementalBagTest.java[tags=ex-collection-elemental-model,indent=0] ---- ==== @@ -324,7 +324,7 @@ include::{coreCollectionTestsDir}/nature/elemental/ElementalBagTest.java[tags=ex ==== [source,java] ---- -include::{coreCollectionTestsDir}/nature/elemental/ElementalBagTest.java[tags=ex-collection-elemental-lifecycle,indent=0] +include::{example-dir-collection}/nature/elemental/ElementalBagTest.java[tags=ex-collection-elemental-lifecycle,indent=0] ---- [source,sql] @@ -382,7 +382,7 @@ cross between the ordered-ness of a `List` and the uniqueness of a `Set`. First ==== [source, JAVA, indent=0] ---- -include::{coreCollectionTestsDir}/semantics/TheEntityWithUniqueList.java[tags=ex-collections-custom-type-model] +include::{example-dir-collection}/semantics/TheEntityWithUniqueList.java[tags=ex-collections-custom-type-model] ---- ==== @@ -393,7 +393,7 @@ The mapping says to use the `UniqueListType` class for the mapping of the plural ==== [source, JAVA, indent=0] ---- -include::{coreCollectionTestsDir}/semantics/UniqueListType.java[tags=collections-custom-type-ex] +include::{example-dir-collection}/semantics/UniqueListType.java[tags=collections-custom-type-ex] ---- ==== @@ -404,7 +404,7 @@ Most custom `UserCollectionType` implementations will want their own `Persistent ==== [source, JAVA, indent=0] ---- -include::{coreCollectionTestsDir}/semantics/UniqueListWrapper.java[tags=collections-custom-semantics-ex] +include::{example-dir-collection}/semantics/UniqueListWrapper.java[tags=collections-custom-semantics-ex] ---- ==== @@ -424,7 +424,7 @@ plural attributes of a given classification, Hibernate also provides the ==== [source, JAVA, indent=0] ---- -include::{coreCollectionTestsDir}/semantics/TheEntityWithUniqueListRegistration.java[tags=ex-collections-custom-type-model] +include::{example-dir-collection}/semantics/TheEntityWithUniqueListRegistration.java[tags=ex-collections-custom-type-model] ---- ==== @@ -484,7 +484,7 @@ Behind the scenes, Hibernate requires an association table to manage the parent- ==== [source,java] ---- -include::{docTestsDir}/UnidirectionalBagTest.java[tags=collections-unidirectional-bag-example,indent=0] +include::{example-dir-collection-doc}/UnidirectionalBagTest.java[tags=collections-unidirectional-bag-example,indent=0] ---- [source,sql] @@ -507,7 +507,7 @@ By marking the parent side with the `CascadeType.ALL` attribute, the unidirectio ==== [source,java] ---- -include::{docTestsDir}/UnidirectionalBagTest.java[tags=collections-unidirectional-bag-lifecycle-example,indent=0] +include::{example-dir-collection-doc}/UnidirectionalBagTest.java[tags=collections-unidirectional-bag-lifecycle-example,indent=0] ---- [source,sql] @@ -536,7 +536,7 @@ The `@ManyToOne` side is the owning side of the bidirectional bag association, w ==== [source,java] ---- -include::{docTestsDir}/BidirectionalBagTest.java[tags=collections-bidirectional-bag-example,indent=0] +include::{example-dir-collection-doc}/BidirectionalBagTest.java[tags=collections-bidirectional-bag-example,indent=0] ---- [source,sql] @@ -550,7 +550,7 @@ include::{extrasdir}/collections-bidirectional-bag-example.sql[] ==== [source,java] ---- -include::{docTestsDir}/BidirectionalBagTest.java[tags=collections-bidirectional-bag-lifecycle-example,indent=0] +include::{example-dir-collection-doc}/BidirectionalBagTest.java[tags=collections-bidirectional-bag-lifecycle-example,indent=0] ---- [source,sql] @@ -564,7 +564,7 @@ include::{extrasdir}/collections-bidirectional-bag-lifecycle-example.sql[] ==== [source,java] ---- -include::{docTestsDir}/BidirectionalBagOrphanRemovalTest.java[tags=collections-bidirectional-bag-orphan-removal-example,indent=0] +include::{example-dir-collection-doc}/BidirectionalBagOrphanRemovalTest.java[tags=collections-bidirectional-bag-orphan-removal-example,indent=0] ---- [source,sql] @@ -594,7 +594,7 @@ When using the `@OrderBy` annotation, the mapping looks as follows: ==== [source,java] ---- -include::{docTestsDir}/UnidirectionalOrderedByListTest.java[tags=collections-unidirectional-ordered-list-order-by-example,indent=0] +include::{example-dir-collection-doc}/UnidirectionalOrderedByListTest.java[tags=collections-unidirectional-ordered-list-order-by-example,indent=0] ---- ==== @@ -626,7 +626,7 @@ Another ordering option is to use the `@OrderColumn` annotation: ==== [source,java] ---- -include::{docTestsDir}/UnidirectionalOrderColumnListTest.java[tags=collections-unidirectional-ordered-list-order-column-example,indent=0] +include::{example-dir-collection-doc}/UnidirectionalOrderColumnListTest.java[tags=collections-unidirectional-ordered-list-order-column-example,indent=0] ---- [source,sql] @@ -659,7 +659,7 @@ The mapping is similar with the <> example, just ==== [source,java] ---- -include::{docTestsDir}/BidirectionalOrderByListTest.java[tags=collections-bidirectional-ordered-list-order-by-example,indent=0] +include::{example-dir-collection-doc}/BidirectionalOrderByListTest.java[tags=collections-bidirectional-ordered-list-order-by-example,indent=0] ---- ==== @@ -672,7 +672,7 @@ When using the `@OrderColumn` annotation, the `order_id` column is going to be e ==== [source,java] ---- -include::{docTestsDir}/BidirectionalOrderColumnListTest.java[tags=collections-bidirectional-ordered-list-order-column-example,indent=0] +include::{example-dir-collection-doc}/BidirectionalOrderColumnListTest.java[tags=collections-bidirectional-ordered-list-order-column-example,indent=0] ---- [source,sql] @@ -693,7 +693,7 @@ You can customize the ordinal of the underlying ordered list by using the https: ==== [source,java] ---- -include::{docTestsDir}/OrderColumnListIndexBaseTest.java[tags=collections-customizing-ordered-list-ordinal-mapping-example,indent=0] +include::{example-dir-collection-doc}/OrderColumnListIndexBaseTest.java[tags=collections-customizing-ordered-list-ordinal-mapping-example,indent=0] ---- ==== @@ -704,7 +704,7 @@ When inserting two `Phone` records, Hibernate is going to start the List index f ==== [source,java] ---- -include::{docTestsDir}/OrderColumnListIndexBaseTest.java[tags=collections-customizing-ordered-list-ordinal-persist-example,indent=0] +include::{example-dir-collection-doc}/OrderColumnListIndexBaseTest.java[tags=collections-customizing-ordered-list-ordinal-persist-example,indent=0] ---- [source,sql] @@ -729,7 +729,7 @@ by the number of characters of the `name` attribute. ==== [source,java] ---- -include::{docTestsDir}/OrderedBySQLTest.java[tags=collections-customizing-ordered-by-sql-clause-mapping-example,indent=0] +include::{example-dir-collection-doc}/OrderedBySQLTest.java[tags=collections-customizing-ordered-by-sql-clause-mapping-example,indent=0] ---- ==== @@ -740,7 +740,7 @@ When fetching the `articles` collection, Hibernate uses the ORDER BY SQL clause ==== [source,java] ---- -include::{docTestsDir}/OrderedBySQLTest.java[tags=collections-customizing-ordered-by-sql-clause-fetching-example,indent=0] +include::{example-dir-collection-doc}/OrderedBySQLTest.java[tags=collections-customizing-ordered-by-sql-clause-fetching-example,indent=0] ---- [source,sql] @@ -764,7 +764,7 @@ The unidirectional set uses a link table to hold the parent-child associations a ==== [source,java] ---- -include::{docTestsDir}/UnidirectionalSetTest.java[tags=collections-unidirectional-set-example,indent=0] +include::{example-dir-collection-doc}/UnidirectionalSetTest.java[tags=collections-unidirectional-set-example,indent=0] ---- ==== @@ -789,7 +789,7 @@ The lifecycle is just like with bidirectional bags except for the duplicates whi ==== [source,java] ---- -include::{docTestsDir}/BidirectionalSetTest.java[tags=collections-bidirectional-set-example,indent=0] +include::{example-dir-collection-doc}/BidirectionalSetTest.java[tags=collections-bidirectional-set-example,indent=0] ---- ==== @@ -809,7 +809,7 @@ A `SortedSet` that relies on the natural sorting order given by the child elemen ==== [source,java] ---- -include::{docTestsDir}/UnidirectionalSortedSetTest.java[tags=collections-unidirectional-sorted-set-natural-comparator-example,indent=0] +include::{example-dir-collection-doc}/UnidirectionalSortedSetTest.java[tags=collections-unidirectional-sorted-set-natural-comparator-example,indent=0] ---- ==== @@ -822,7 +822,7 @@ To provide a custom sorting logic, Hibernate also provides a `@SortComparator` a ==== [source,java] ---- -include::{docTestsDir}/UnidirectionalComparatorSortedSetTest.java[tags=collections-unidirectional-sorted-set-custom-comparator-example,indent=0] +include::{example-dir-collection-doc}/UnidirectionalComparatorSortedSetTest.java[tags=collections-unidirectional-sorted-set-custom-comparator-example,indent=0] ---- ==== @@ -836,9 +836,9 @@ The `@SortNatural` and `@SortComparator` work the same for bidirectional sorted ==== [source,java] ---- -include::{docTestsDir}/BidirectionalSortedSetTest.java[tags=collections-bidirectional-sorted-set-example,indent=0] +include::{example-dir-collection-doc}/BidirectionalSortedSetTest.java[tags=collections-bidirectional-sorted-set-example,indent=0] -include::{docTestsDir}/UnidirectionalComparatorSortedSetTest.java[lines=75..77,indent=0] +include::{example-dir-collection-doc}/UnidirectionalComparatorSortedSetTest.java[lines=75..77,indent=0] ---- ==== @@ -871,7 +871,7 @@ A map of value type must use the `@ElementCollection` annotation, just like valu ==== [source,java] ---- -include::{docTestsDir}/ElementCollectionMapTest.java[tags=collections-map-value-type-entity-key-example,indent=0] +include::{example-dir-collection-doc}/ElementCollectionMapTest.java[tags=collections-map-value-type-entity-key-example,indent=0] ---- [source,sql] @@ -887,7 +887,7 @@ Adding entries to the map generates the following SQL statements: ==== [source,java] ---- -include::{docTestsDir}/ElementCollectionMapTest.java[tags=collections-map-value-type-entity-key-add-example,indent=0] +include::{example-dir-collection-doc}/ElementCollectionMapTest.java[tags=collections-map-value-type-entity-key-add-example,indent=0] ---- [source,sql] @@ -925,7 +925,7 @@ Since we want to map all the calls by their associated `java.util.Date`, not by ==== [source,java] ---- -include::{docTestsDir}/MapKeyTypeTest.java[tags=collections-map-custom-key-type-mapping-example,indent=0] +include::{example-dir-collection-doc}/MapKeyTypeTest.java[tags=collections-map-custom-key-type-mapping-example,indent=0] ---- ==== @@ -940,7 +940,7 @@ Considering you have the following `PhoneNumber` interface with an implementatio ==== [source,java] ---- -include::{docTestsDir}/MapKeyClassTest.java[tags=collections-map-key-class-type-mapping-example,indent=0] +include::{example-dir-collection-doc}/MapKeyClassTest.java[tags=collections-map-key-class-type-mapping-example,indent=0] ---- ==== @@ -952,7 +952,7 @@ If you want to use the `PhoneNumber` interface as a `java.util.Map` key, then yo ==== [source,java] ---- -include::{docTestsDir}/MapKeyClassTest.java[tags=collections-map-key-class-mapping-example,indent=0] +include::{example-dir-collection-doc}/MapKeyClassTest.java[tags=collections-map-key-class-mapping-example,indent=0] ---- [source,sql] @@ -969,7 +969,7 @@ Hibernate generates the following SQL statements: ==== [source,java] ---- -include::{docTestsDir}/MapKeyClassTest.java[tags=collections-map-key-class-persist-example,indent=0] +include::{example-dir-collection-doc}/MapKeyClassTest.java[tags=collections-map-key-class-persist-example,indent=0] ---- [source,sql] @@ -986,7 +986,7 @@ Hibernate generates the following SQL statements: ==== [source,java] ---- -include::{docTestsDir}/MapKeyClassTest.java[tags=collections-map-key-class-fetch-example,indent=0] +include::{example-dir-collection-doc}/MapKeyClassTest.java[tags=collections-map-key-class-fetch-example,indent=0] ---- [source,sql] @@ -1013,7 +1013,7 @@ The `@MapKey` annotation is used to define the entity attribute used as a key of ==== [source,java] ---- -include::{docTestsDir}/UnidirectionalMapTest.java[tags=collections-map-unidirectional-example,indent=0] +include::{example-dir-collection-doc}/UnidirectionalMapTest.java[tags=collections-map-unidirectional-example,indent=0] ---- [source,sql] @@ -1034,7 +1034,7 @@ In the following example, you can see that `@MapKeyEnumerated` was used so that ==== [source,java] ---- -include::{docTestsDir}/BidirectionalMapTest.java[tags=collections-map-bidirectional-example,indent=0] +include::{example-dir-collection-doc}/BidirectionalMapTest.java[tags=collections-map-bidirectional-example,indent=0] ---- [source,sql] @@ -1065,7 +1065,7 @@ By default, Hibernate will choose a BINARY type, as supported by the current `Di ==== [source,java] ---- -include::{docTestsDir}/ArrayTest.java[tags=collections-array-binary-example,indent=0] +include::{example-dir-collection-doc}/ArrayTest.java[tags=collections-array-binary-example,indent=0] ---- [source,sql] @@ -1100,9 +1100,9 @@ is using an <>. ==== [source,java] ---- -include::{coreCollectionTestsDir}/asbasic/CommaDelimitedStringsConverter.java[tags=ex-csv-converter,indent=0] +include::{example-dir-collection}/asbasic/CommaDelimitedStringsConverter.java[tags=ex-csv-converter,indent=0] -include::{coreCollectionTestsDir}/asbasic/CommaDelimitedStringsConverterTests.java[tags=ex-csv-converter-model,indent=0] +include::{example-dir-collection}/asbasic/CommaDelimitedStringsConverterTests.java[tags=ex-csv-converter-model,indent=0] ---- ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/customizing.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/customizing.adoc index fd55cba3160b..438815a0659a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/customizing.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/customizing.adoc @@ -1,8 +1,8 @@ [[domain-customizing]] === Customizing the domain model -:rootProjectDir: ../../../../../../.. -:coreProjectDir: {rootProjectDir}/hibernate-core -:attributeBinderTestDir: {coreProjectDir}/src/test/java/org/hibernate/orm/test/mapping/attributebinder +:root-project-dir: ../../../../../../.. +:core-project-dir: {root-project-dir}/hibernate-core +:example-dir-attributebinder: {core-project-dir}/src/test/java/org/hibernate/orm/test/mapping/attributebinder :extrasdir: extras For cases where Hibernate does not provide a built-in way to configure the domain @@ -18,9 +18,9 @@ An example: ==== [source,java] ---- -include::{attributeBinderTestDir}/YesNo.java[tag=attribute-binder-example, indent=0] +include::{example-dir-attributebinder}/YesNo.java[tag=attribute-binder-example, indent=0] -include::{attributeBinderTestDir}/YesNoBinder.java[tag=attribute-binder-example, indent=0] +include::{example-dir-attributebinder}/YesNoBinder.java[tag=attribute-binder-example, indent=0] ---- ==== @@ -32,4 +32,4 @@ it has the `@AttributeBinderType` meta-annotation and knows how to apply that th Notice also that `@AttributeBinderType` provides a type-safe way to perform configuration because the `AttributeBinder` (`YesNoBinder`) is handed the custom annotation (`@YesNo`) to grab its configured attributes. `@YesNo` does not provide any attributes, but it easily could. Whatever `YesNoBinder` -supports. \ No newline at end of file +supports. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc index 26e914897afb..b456d4f95e46 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/dynamic_model.adoc @@ -1,7 +1,9 @@ [[dynamic-model]] === Dynamic Model -:sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping/dynamic -:mappingdir: ../../../../../test/resources/org/hibernate/userguide/mapping/dynamic +:root-project-dir: ../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:example-dir-dynamic: {documentation-project-dir}/src/test/java/org/hibernate/userguide/mapping/dynamic +:example-dir-resources: {documentation-project-dir}/src/test/resources/org/hibernate/userguide/mapping/dynamic :extrasdir: extras [IMPORTANT] @@ -26,7 +28,7 @@ Entity modes can now be mixed within a domain model; a dynamic entity might refe ==== [source,xml] ---- -include::{mappingdir}/Book.hbm.xml[tag=mapping-model-dynamic-example, indent=0] +include::{example-dir-resources}/Book.hbm.xml[tag=mapping-model-dynamic-example, indent=0] ---- ==== @@ -37,7 +39,7 @@ After you defined your entity mapping, you need to instruct Hibernate to use the ==== [source,java] ---- -include::{sourcedir}/DynamicEntityTest.java[tag=mapping-model-dynamic-setting-example, indent=0] +include::{example-dir-dynamic}/DynamicEntityTest.java[tag=mapping-model-dynamic-setting-example, indent=0] ---- ==== @@ -49,7 +51,7 @@ Hibernate is going to generate the following SQL statement: ==== [source,java] ---- -include::{sourcedir}/DynamicEntityTest.java[tag=mapping-model-dynamic-example, indent=0] +include::{example-dir-dynamic}/DynamicEntityTest.java[tag=mapping-model-dynamic-example, indent=0] ---- [source,sql] @@ -66,4 +68,4 @@ However, as a result of the Hibernate mapping, the database schema can easily be It is also interesting to note that dynamic models are great for certain integration use cases as well. Envers, for example, makes extensive use of dynamic models to represent the historical data. -==== \ No newline at end of file +==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc index 098dc1d649d0..2c5b1f1e70e4 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc @@ -1,11 +1,13 @@ [[embeddables]] === Embeddable values -:rootProjectDir: ../../../../../../.. -:sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping/embeddable -:coreProjectDir: {rootProjectDir}/hibernate-core -:coreTestSrcDir: {rootProjectDir}/hibernate-core/src/test/java -:instantiatorTestDir: {coreTestSrcDir}/org/hibernate/orm/test/mapping/embeddable/strategy/instantiator -:usertypeTestDir: {coreTestSrcDir}/org/hibernate/orm/test/mapping/embeddable/strategy/usertype +:root-project-dir: ../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:documentation-example-base: {documentation-project-dir}/src/test/java +:example-dir-emeddable: {documentation-example-base}/org/hibernate/userguide/mapping/embeddable +:core-project-dir: {root-project-dir}/hibernate-core +:core-test-base: {root-project-dir}/hibernate-core/src/test/java +:example-dir-embeddableinstantiator: {core-test-base}/org/hibernate/orm/test/mapping/embeddable/strategy/instantiator +:example-dir-compositeusertype: {core-test-base}/org/hibernate/orm/test/mapping/embeddable/strategy/usertype :extrasdir: extras Historically Hibernate called these components. @@ -28,7 +30,7 @@ Throughout this chapter and thereafter, for brevity sake, embeddable types may a ==== [source,java] ---- -include::{sourcedir}/NestedEmbeddableTest.java[tag=embeddable-type-mapping-example, indent=0] +include::{example-dir-emeddable}/NestedEmbeddableTest.java[tag=embeddable-type-mapping-example, indent=0] ---- ==== @@ -45,7 +47,7 @@ Most often, embeddable types are used to group multiple basic type mappings and ==== [source,java] ---- -include::{sourcedir}/SimpleEmbeddableTest.java[tag=embeddable-type-mapping-example, indent=0] +include::{example-dir-emeddable}/SimpleEmbeddableTest.java[tag=embeddable-type-mapping-example, indent=0] ---- [source,sql] @@ -75,7 +77,7 @@ In fact, that table could also be mapped by the following entity type instead. ==== [source,java] ---- -include::{sourcedir}/SimpleEmbeddableEquivalentTest.java[tag=embeddable-type-mapping-example, indent=0] +include::{example-dir-emeddable}/SimpleEmbeddableEquivalentTest.java[tag=embeddable-type-mapping-example, indent=0] ---- ==== @@ -106,7 +108,7 @@ which defines a `@ManyToOne` association with the `Country` entity: ==== [source,java] ---- -include::{sourcedir}/EmbeddableOverrideTest.java[tag=embeddable-type-association-mapping-example, indent=0] +include::{example-dir-emeddable}/EmbeddableOverrideTest.java[tag=embeddable-type-association-mapping-example, indent=0] ---- [source,sql] @@ -125,7 +127,7 @@ Therefore, the `Book` entity needs to override the embeddable type mappings for ==== [source,java] ---- -include::{sourcedir}/EmbeddableOverrideTest.java[tag=embeddable-type-override-mapping-example, indent=0] +include::{example-dir-emeddable}/EmbeddableOverrideTest.java[tag=embeddable-type-override-mapping-example, indent=0] ---- [source,sql] @@ -178,7 +180,7 @@ However, for simple embeddable types, there is no such construct and so you need ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/TargetTest.java[tags=embeddable-Target-example] +include::{example-dir-emeddable}/TargetTest.java[tags=embeddable-Target-example] ---- ==== @@ -193,7 +195,7 @@ Assuming we have persisted the following `City` entity: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/TargetTest.java[tags=embeddable-Target-persist-example] +include::{example-dir-emeddable}/TargetTest.java[tags=embeddable-Target-persist-example] ---- ==== @@ -204,7 +206,7 @@ When fetching the `City` entity, the `coordinates` property is mapped by the `@T ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/TargetTest.java[tags=embeddable-Target-fetching-example] +include::{example-dir-emeddable}/TargetTest.java[tags=embeddable-Target-fetching-example] ---- [source, SQL, indent=0] @@ -225,7 +227,7 @@ The Hibernate-specific `@Parent` annotation allows you to reference the owner en ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/ParentTest.java[tags=embeddable-Parent-example] +include::{example-dir-emeddable}/ParentTest.java[tags=embeddable-Parent-example] ---- ==== @@ -236,7 +238,7 @@ Assuming we have persisted the following `City` entity: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/ParentTest.java[tags=embeddable-Parent-persist-example] +include::{example-dir-emeddable}/ParentTest.java[tags=embeddable-Parent-persist-example] ---- ==== @@ -247,7 +249,7 @@ When fetching the `City` entity, the `city` property of the embeddable type acts ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/ParentTest.java[tags=embeddable-Parent-fetching-example] +include::{example-dir-emeddable}/ParentTest.java[tags=embeddable-Parent-fetching-example] ---- ==== @@ -270,7 +272,7 @@ embeddable: ==== [source, JAVA, indent=0] ---- -include::{instantiatorTestDir}/embedded/Name.java[tags=embeddable-instantiator-embeddable] +include::{example-dir-embeddableinstantiator}/embedded/Name.java[tags=embeddable-instantiator-embeddable] ---- ==== @@ -282,7 +284,7 @@ conventions, in terms of constructor, a custom strategy for instantiation is nee ==== [source, JAVA, indent=0] ---- -include::{instantiatorTestDir}/embedded/NameInstantiator.java[tags=embeddable-instantiator-impl] +include::{example-dir-embeddableinstantiator}/embedded/NameInstantiator.java[tags=embeddable-instantiator-impl] ---- ==== @@ -294,7 +296,7 @@ annotation can be used on the embedded attribute: ==== [source, JAVA, indent=0] ---- -include::{instantiatorTestDir}/embedded/Person.java[tags=embeddable-instantiator-property] +include::{example-dir-embeddableinstantiator}/embedded/Person.java[tags=embeddable-instantiator-property] ---- ==== @@ -305,9 +307,9 @@ include::{instantiatorTestDir}/embedded/Person.java[tags=embeddable-instantiator ==== [source, JAVA, indent=0] ---- -include::{instantiatorTestDir}/embeddable/Name.java[tags=embeddable-instantiator-class] +include::{example-dir-embeddableinstantiator}/embeddable/Name.java[tags=embeddable-instantiator-class] -include::{instantiatorTestDir}/embeddable/Person.java[tags=embeddable-instantiator-class] +include::{example-dir-embeddableinstantiator}/embeddable/Person.java[tags=embeddable-instantiator-class] ---- ==== @@ -321,7 +323,7 @@ on the <>. ==== [source, JAVA, indent=0] ---- -include::{instantiatorTestDir}/registered/Person.java[tags=embeddable-instantiator-registration] +include::{example-dir-embeddableinstantiator}/registered/Person.java[tags=embeddable-instantiator-registration] ---- ==== @@ -346,7 +348,7 @@ For example, consider the following custom type: ==== [source, JAVA, indent=0] ---- -include::{usertypeTestDir}/embedded/Name.java[tags=embeddable-usertype-domain] +include::{example-dir-compositeusertype}/embedded/Name.java[tags=embeddable-usertype-domain] ---- ==== @@ -358,7 +360,7 @@ conventions, a custom user type for instantiation and state access is needed. ==== [source, JAVA, indent=0] ---- -include::{usertypeTestDir}/embedded/NameCompositeUserType.java[tags=embeddable-usertype-impl] +include::{example-dir-compositeusertype}/embedded/NameCompositeUserType.java[tags=embeddable-usertype-impl] ---- ==== @@ -381,7 +383,7 @@ annotation can be used on the embedded and element collection attributes: ==== [source, JAVA, indent=0] ---- -include::{usertypeTestDir}/embedded/Person.java[tags=embeddable-usertype-property] +include::{example-dir-compositeusertype}/embedded/Person.java[tags=embeddable-usertype-property] ---- ==== @@ -393,7 +395,7 @@ when the application developer wants to apply the composite user type for all do ==== [source, JAVA, indent=0] ---- -include::{usertypeTestDir}/registered/Person.java[tags=embeddable-usertype-registration] +include::{example-dir-compositeusertype}/registered/Person.java[tags=embeddable-usertype-registration] ---- ==== @@ -417,7 +419,7 @@ However, for the purposes of this discussion, Hibernate has the capability to in ==== [source,java] ---- -include::{sourcedir}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-namingstrategy-entity-mapping, indent=0] +include::{example-dir-emeddable}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-namingstrategy-entity-mapping, indent=0] ---- ==== @@ -428,7 +430,7 @@ To make it work, you need to use the `ImplicitNamingStrategyComponentPathImpl` n ==== [source,java] ---- -include::{sourcedir}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-ImplicitNamingStrategyComponentPathImpl, indent=0] +include::{example-dir-emeddable}/EmbeddableImplicitOverrideTest.java[tag=embeddable-multiple-ImplicitNamingStrategyComponentPathImpl, indent=0] ---- ==== @@ -439,4 +441,4 @@ Now the "path" to attributes are used in the implicit column naming: include::{extrasdir}/embeddable/embeddable-multiple-namingstrategy-entity-mapping.sql[] ---- -You could even develop your own naming strategy to do other types of implicit naming strategies. \ No newline at end of file +You could even develop your own naming strategy to do other types of implicit naming strategies. diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc index 1481ae5227e1..fe5ae7670e11 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/entity.adoc @@ -1,9 +1,11 @@ [[entity]] === Entity types -:sourcedir-locking: ../../../../../test/java/org/hibernate/userguide/locking -:sourcedir-mapping: ../../../../../test/java/org/hibernate/userguide/mapping -:sourcedir-proxy: ../../../../../test/java/org/hibernate/userguide/proxy -:sourcedir-persister: ../../../../../test/java/org/hibernate/userguide/persister +:root-project-dir: ../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:example-dir-locking: {documentation-project-dir}/src/test/java/org/hibernate/userguide/locking +:example-dir-mapping: {documentation-project-dir}/src/test/java/org/hibernate/userguide/mapping +:example-dir-proxy: {documentation-project-dir}/src/test/java/org/hibernate/userguide/proxy +:example-dir-persister: {documentation-project-dir}/src/test/java/org/hibernate/userguide/persister :extrasdir: extras .Usage of the word _entity_ @@ -105,7 +107,7 @@ The placement of the `@Id` annotation marks the <> for details. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CriteriaTest.java[tags=criteria-typedquery-multiselect-array-explicit-example] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-typedquery-multiselect-array-explicit-example] ---- ==== @@ -111,7 +113,7 @@ The example then uses the array method of `jakarta.persistence.criteria.Criteria ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CriteriaTest.java[tags=criteria-typedquery-multiselect-array-implicit-example] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-typedquery-multiselect-array-implicit-example] ---- ==== @@ -131,9 +133,9 @@ Going back to the example query there, rather than returning an array of _[Perso ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/PersonWrapper.java[tags=criteria-typedquery-wrapper-example, indent=0] +include::{example-dir-criteria}/PersonWrapper.java[tags=criteria-typedquery-wrapper-example, indent=0] -include::{sourcedir}/CriteriaTest.java[tags=criteria-typedquery-wrapper-example, indent=0] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-typedquery-wrapper-example, indent=0] ---- ==== @@ -155,7 +157,7 @@ A better approach to <> is to use either a wrap ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CriteriaTest.java[tags=criteria-tuple-example] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-tuple-example] ---- ==== @@ -218,7 +220,7 @@ A root is always an entity type. Roots are defined and added to the criteria by ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CriteriaTest.java[tags=criteria-from-root-example] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-from-root-example] ---- ==== @@ -230,7 +232,7 @@ Here is an example defining a Cartesian Product between `Person` and `Partner` e ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CriteriaTest.java[tags=criteria-from-multiple-root-example] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-from-multiple-root-example] ---- ==== @@ -245,7 +247,7 @@ Joins are created by the numerous overloaded __join__ methods of the `jakarta.pe ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CriteriaTest.java[tags=criteria-from-join-example] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-from-join-example] ---- ==== @@ -260,7 +262,7 @@ Fetches are created by the numerous overloaded __fetch__ methods of the `jakarta ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CriteriaTest.java[tags=criteria-from-fetch-example] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-from-fetch-example] ---- ==== @@ -286,7 +288,7 @@ Roots, joins and fetches are themselves path expressions as well. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CriteriaTest.java[tags=criteria-param-example] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-param-example] ---- ==== @@ -301,6 +303,6 @@ Then use the parameter reference to bind the parameter value to the `jakarta.per ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CriteriaTest.java[tags=criteria-group-by-example] +include::{example-dir-criteria}/CriteriaTest.java[tags=criteria-group-by-example] ---- ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/Query.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/Query.adoc index 4dda3dcc3256..f990607b1590 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/Query.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/Query.adoc @@ -1,7 +1,9 @@ [[hql]] == Java API for HQL and JPQL -:modeldir: ../../../../../../main/java/org/hibernate/userguide/model -:sourcedir: ../../../../../../test/java/org/hibernate/userguide/hql +:root-project-dir: ../../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:example-dir-model: {documentation-project-dir}/src/main/java/org/hibernate/userguide/model +:example-dir-query: {documentation-project-dir}/src/test/java/org/hibernate/userguide/hql :extrasdir: extras The Hibernate Query Language (HQL) and the Java Persistence Query Language (JPQL) are object-oriented query languages based on SQL and very similar in flavor to SQL. @@ -37,23 +39,23 @@ The code examples featured in this chapter, and the next, make use of the follow ==== [source, JAVA, indent=0] ---- -include::{modeldir}/Person.java[tags=hql-examples-domain-model-example] +include::{example-dir-model}/Person.java[tags=hql-examples-domain-model-example] -include::{modeldir}/AddressType.java[tags=hql-examples-domain-model-example] +include::{example-dir-model}/AddressType.java[tags=hql-examples-domain-model-example] -include::{modeldir}/Partner.java[tags=hql-examples-domain-model-example] +include::{example-dir-model}/Partner.java[tags=hql-examples-domain-model-example] -include::{modeldir}/Phone.java[tags=hql-examples-domain-model-example] +include::{example-dir-model}/Phone.java[tags=hql-examples-domain-model-example] -include::{modeldir}/PhoneType.java[tags=hql-examples-domain-model-example] +include::{example-dir-model}/PhoneType.java[tags=hql-examples-domain-model-example] -include::{modeldir}/Call.java[tags=hql-examples-domain-model-example] +include::{example-dir-model}/Call.java[tags=hql-examples-domain-model-example] -include::{modeldir}/Payment.java[tags=hql-examples-domain-model-example] +include::{example-dir-model}/Payment.java[tags=hql-examples-domain-model-example] -include::{modeldir}/CreditCardPayment.java[tags=hql-examples-domain-model-example] +include::{example-dir-model}/CreditCardPayment.java[tags=hql-examples-domain-model-example] -include::{modeldir}/WireTransferPayment.java[tags=hql-examples-domain-model-example] +include::{example-dir-model}/WireTransferPayment.java[tags=hql-examples-domain-model-example] ---- ==== @@ -79,7 +81,7 @@ Named queries may be defined using the Jakarta Persistence annotation `@NamedQue ==== [source, JAVA, indent=0] ---- -include::{modeldir}/Person.java[tags=jpa-read-only-entities-native-example] +include::{example-dir-model}/Person.java[tags=jpa-read-only-entities-native-example] ---- ==== @@ -92,7 +94,7 @@ which allows the specification of additional properties of the query, including ==== [source, JAVA, indent=0] ---- -include::{modeldir}/Phone.java[tags=jpql-api-hibernate-named-query-example, indent=0] +include::{example-dir-model}/Phone.java[tags=jpql-api-hibernate-named-query-example, indent=0] ---- //include::{sourcedir}/HQLTest.java[tags=jpql-api-hibernate-named-query-example, indent=0] ==== @@ -126,7 +128,7 @@ That way, you'll obtain a `TypedQuery`, and avoid some later typecasting. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=jpql-api-example] +include::{example-dir-query}/HQLTest.java[tags=jpql-api-example] ---- ==== @@ -135,9 +137,9 @@ include::{sourcedir}/HQLTest.java[tags=jpql-api-example] ==== [source, JAVA, indent=0] ---- -include::{modeldir}/Person.java[tags=jpql-api-named-query-example, indent=0] +include::{example-dir-model}/Person.java[tags=jpql-api-named-query-example, indent=0] -include::{sourcedir}/HQLTest.java[tags=jpql-api-named-query-example, indent=0] +include::{example-dir-query}/HQLTest.java[tags=jpql-api-named-query-example, indent=0] ---- ==== @@ -161,7 +163,7 @@ Hibernate's `Query` interface offers additional operations not available via `Ty ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-api-example] +include::{example-dir-query}/HQLTest.java[tags=hql-api-example] ---- ==== @@ -170,7 +172,7 @@ include::{sourcedir}/HQLTest.java[tags=hql-api-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-api-named-query-example] +include::{example-dir-query}/HQLTest.java[tags=hql-api-named-query-example] ---- ==== @@ -194,7 +196,7 @@ If the query has parameters, arguments must be bound to each parameter before th ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=jpql-api-parameter-example] +include::{example-dir-query}/HQLTest.java[tags=jpql-api-parameter-example] ---- ==== @@ -206,7 +208,7 @@ Just like with named parameters, a ordinal parameter may appear multiple times i ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=jpql-api-ordinal-parameter-example] +include::{example-dir-query}/HQLTest.java[tags=jpql-api-ordinal-parameter-example] ---- ==== @@ -230,7 +232,7 @@ The `Query` interface is used to control the execution of the query. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=jpql-api-list-example] +include::{example-dir-query}/HQLTest.java[tags=jpql-api-list-example] ---- ==== @@ -239,7 +241,7 @@ include::{sourcedir}/HQLTest.java[tags=jpql-api-list-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=jpql-api-single-result-example] +include::{example-dir-query}/HQLTest.java[tags=jpql-api-single-result-example] ---- ==== @@ -248,7 +250,7 @@ include::{sourcedir}/HQLTest.java[tags=jpql-api-single-result-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=jpql-api-stream-example] +include::{example-dir-query}/HQLTest.java[tags=jpql-api-stream-example] ---- ==== @@ -268,7 +270,7 @@ The very important methods `Query#setMaxResults()` and `Query#setFirstResult()` ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=jpql-api-basic-usage-example] +include::{example-dir-query}/HQLTest.java[tags=jpql-api-basic-usage-example] ---- ==== @@ -283,7 +285,7 @@ For example, we may want to specify an execution timeout or control caching. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=jpql-api-hint-usage-example] +include::{example-dir-query}/HQLTest.java[tags=jpql-api-hint-usage-example] ---- ==== @@ -352,7 +354,7 @@ For complete details, see the https://docs.jboss.org/hibernate/orm/{majorMinorVe ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-api-basic-usage-example] +include::{example-dir-query}/HQLTest.java[tags=hql-api-basic-usage-example] ---- ==== @@ -368,7 +370,7 @@ Hibernate provides several some built-in implementations of these interfaces, fo ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/SelectDistinctTest.java[tags=hql-distinct-entity-resulttransformer-example] +include::{example-dir-query}/SelectDistinctTest.java[tags=hql-distinct-entity-resulttransformer-example] ---- ==== @@ -470,7 +472,7 @@ Read-only entities are skipped by the dirty checking mechanism as illustrated by ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-read-only-entities-example] +include::{example-dir-query}/HQLTest.java[tags=hql-read-only-entities-example] ---- [source, SQL, indent=0] @@ -488,7 +490,7 @@ The method `Query#setReadOnly()` is an alternative to using a Jakarta Persistenc ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-read-only-entities-native-example] +include::{example-dir-query}/HQLTest.java[tags=hql-read-only-entities-native-example] ---- ==== @@ -508,7 +510,7 @@ Depending on the specified `ScrollMode`, and on the capabilities of the JDBC dri ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-api-scroll-example] +include::{example-dir-query}/HQLTest.java[tags=hql-api-scroll-example] ---- ==== @@ -541,7 +543,7 @@ For that, use `getResultList().stream()`. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-api-stream-projection-example] +include::{example-dir-query}/HQLTest.java[tags=hql-api-stream-projection-example] ---- ==== @@ -550,7 +552,7 @@ include::{sourcedir}/HQLTest.java[tags=hql-api-stream-projection-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-api-stream-example] +include::{example-dir-query}/HQLTest.java[tags=hql-api-stream-example] ---- ==== @@ -593,9 +595,9 @@ it does not expose a `#executeUpdate` method. This allows for earlier validatio ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/SelectionQueryExampleTests.java[tags=example-hql-selection-query] +include::{example-dir-query}/SelectionQueryExampleTests.java[tags=example-hql-selection-query] -include::{sourcedir}/SelectionQueryExampleTests.java[tags=example-hql-selection-query-query] +include::{example-dir-query}/SelectionQueryExampleTests.java[tags=example-hql-selection-query-query] ---- ==== @@ -606,9 +608,9 @@ include::{sourcedir}/SelectionQueryExampleTests.java[tags=example-hql-selection- ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/SelectionQueryExampleTests.java[tags=example-hql-named-selection-query] +include::{example-dir-query}/SelectionQueryExampleTests.java[tags=example-hql-named-selection-query] -include::{sourcedir}/SelectionQueryExampleTests.java[tags=example-hql-named-selection-query-query] +include::{example-dir-query}/SelectionQueryExampleTests.java[tags=example-hql-named-selection-query-query] ---- ==== @@ -626,9 +628,9 @@ For example, in terms of execution, it only exposes `#executeUpdate` method. Th ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/MutationQueryExampleTests.java[tags=example-hql-mutation-query] +include::{example-dir-query}/MutationQueryExampleTests.java[tags=example-hql-mutation-query] -include::{sourcedir}/MutationQueryExampleTests.java[tags=example-hql-mutation-query-query] +include::{example-dir-query}/MutationQueryExampleTests.java[tags=example-hql-mutation-query-query] ---- ==== @@ -640,9 +642,9 @@ include::{sourcedir}/MutationQueryExampleTests.java[tags=example-hql-mutation-qu ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/MutationQueryExampleTests.java[tags=example-hql-named-mutation-query] +include::{example-dir-query}/MutationQueryExampleTests.java[tags=example-hql-named-mutation-query] -include::{sourcedir}/MutationQueryExampleTests.java[tags=example-hql-named-mutation-query-query] +include::{example-dir-query}/MutationQueryExampleTests.java[tags=example-hql-named-mutation-query-query] ---- ==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc index 9a2aafbd2dd7..13c81154ae1f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -1,7 +1,9 @@ [[query-language]] == Hibernate Query Language -:modeldir: ../../../../../../main/java/org/hibernate/userguide/model -:sourcedir: ../../../../../../test/java/org/hibernate/userguide/hql +:root-project-dir: ../../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:example-dir-model: {documentation-project-dir}/src/main/java/org/hibernate/userguide/model +:example-dir-hql: ../../../../../../test/java/org/hibernate/userguide/hql :extrasdir: extras This chapter describes Hibernate Query Language (HQL) and Jakarta Persistence Query Language (JPQL). @@ -107,7 +109,7 @@ For example, the simplest query in HQL has no `select` clause at all: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-select-simplest-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-simplest-example] ---- ==== @@ -121,14 +123,14 @@ Naturally, the previous query may be written with a `select` clause: [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-select-simplest-jpql-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-simplest-jpql-example] ---- When there's no explicit `select` clause, the select list is implied by the result type of the query: [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-select-no-from] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-no-from] ---- For complicated queries, it's probably best to explicitly specify a `select` list. @@ -140,7 +142,7 @@ An alternative "simplest" query has _only_ a `select` list: ==== [source, SQL, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-select-simplest-example-alt] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-simplest-example-alt] ---- ==== @@ -155,7 +157,7 @@ But it's more natural to put it last: [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-select-last-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-last-example] ---- This form of the query is more readable, because the alias is declared _before_ it's used, just as God and nature intended. @@ -182,7 +184,7 @@ For example: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-update-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-update-example] ---- ==== @@ -194,9 +196,9 @@ A single HQL `update` statement might result in multiple SQL update statements e ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/../batch/BatchTest.java[tags=batch-bulk-jpql-update-example] +include::{example-dir-hql}/../batch/BatchTest.java[tags=batch-bulk-jpql-update-example] -include::{sourcedir}/../batch/BatchTest.java[tags=batch-bulk-hql-update-example] +include::{example-dir-hql}/../batch/BatchTest.java[tags=batch-bulk-hql-update-example] ---- ==== @@ -222,7 +224,7 @@ Adding the keyword `versioned`—writing `update versioned`—specifies ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/../batch/BatchTest.java[tags=batch-bulk-hql-update-version-example] +include::{example-dir-hql}/../batch/BatchTest.java[tags=batch-bulk-hql-update-version-example] ---- ==== @@ -288,12 +290,12 @@ For example: ==== [source, SQL, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-insert-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-insert-example] ---- [source, SQL, indent=0] ---- -include::{sourcedir}/../batch/BatchTest.java[tags=batch-bulk-hql-insert-example] +include::{example-dir-hql}/../batch/BatchTest.java[tags=batch-bulk-hql-insert-example] ---- ==== @@ -357,7 +359,7 @@ To escape a single quote within a string literal, use a doubled single quote: `' ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-string-literals-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-string-literals-example] ---- ==== @@ -371,7 +373,7 @@ Numeric literals come in several different forms. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-numeric-literals-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-numeric-literals-example] ---- ==== @@ -472,7 +474,7 @@ Literal values of a Java enumerated type may be written without needing to speci ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-enum-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-enum-example] ---- ==== @@ -487,7 +489,7 @@ HQL allows any Java `static` constant to be used in HQL, but it must be referenc ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-java-constant-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-java-constant-example] ---- ==== @@ -516,7 +518,7 @@ See <> for details of the `concat()` function. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-concatenation-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-concatenation-example] ---- ==== @@ -532,7 +534,7 @@ The basic SQL arithmetic operators, `+`,`-`,`*`, and `/` are joined by the remai ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-numeric-arithmetic-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-numeric-arithmetic-example] ---- ==== @@ -615,7 +617,7 @@ For example: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-simple-case-expressions-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-simple-case-expressions-example] ---- ==== @@ -637,7 +639,7 @@ For example: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-searched-case-expressions-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-searched-case-expressions-example] ---- ==== @@ -648,7 +650,7 @@ A `case` expression may contain complex expression, including operator expressio ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-case-arithmetic-expressions-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-case-arithmetic-expressions-example] ---- ==== @@ -703,7 +705,7 @@ This is mainly useful when dealing with entity inheritance hierarchies. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-entity-type-exp-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-entity-type-exp-example] ---- ==== @@ -717,7 +719,7 @@ This is useful when dealing with entity inheritance hierarchies. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-treat-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-treat-example] ---- ==== @@ -738,7 +740,7 @@ The target type is an unqualified Java class name: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-cast-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-cast-function-example] ---- ==== @@ -750,7 +752,7 @@ The function `str(x)` is a synonym for `cast(x as String)`. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-str-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-str-function-example] ---- ==== @@ -775,7 +777,7 @@ An abbreviated `case` expression that returns the first non-null operand. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-coalesce-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-coalesce-example] ---- ==== @@ -791,7 +793,7 @@ Evaluates to null if its operands are equal, or to its first argument otherwise. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-nullif-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-nullif-example] ---- ==== @@ -818,7 +820,7 @@ For a full list of field types, see the Javadoc for https://docs.jboss.org/hiber ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-extract-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-extract-function-example] ---- ==== @@ -840,7 +842,7 @@ TIP: These abbreviations aren't part of the JPQL standard, but on the other hand ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-year-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-year-function-example] ---- ==== @@ -892,7 +894,7 @@ Accepts a variable number of arguments, and produces a string by concatenating t ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-concat-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-concat-function-example] ---- ==== @@ -904,7 +906,7 @@ The JPQL function `locate()` determines the position of a substring within anoth ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-locate-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-locate-function-example] ---- ==== @@ -915,7 +917,7 @@ The `position()` function has a similar purpose, but follows the ANSI SQL syntax ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-position-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-position-function-example] ---- ==== @@ -928,7 +930,7 @@ Returns a substring of the given string. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-substring-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-substring-function-example] ---- ==== @@ -939,7 +941,7 @@ It may be used to trim `leading` characters, `trailing` characters, or both. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-trim-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-trim-function-example] ---- ==== @@ -1015,15 +1017,15 @@ Of course, we also have a number of functions for working with numeric values. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-abs-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-abs-function-example] ---- [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-mod-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-mod-function-example] ---- [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-sqrt-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-sqrt-function-example] ---- ==== @@ -1057,7 +1059,7 @@ The number of elements of a collection or to-many association. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-size-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-size-example] ---- ==== @@ -1124,7 +1126,7 @@ Then at startup Hibernate will log a list of type signatures of all registered f ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-native-function-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-native-function-example] ---- ==== @@ -1170,7 +1172,7 @@ The operands should be of the same type. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-relational-comparisons-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-relational-comparisons-example] ---- ==== @@ -1186,7 +1188,7 @@ Of course, all three operands must be of compatible type. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-between-predicate-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-between-predicate-example] ---- ==== @@ -1207,7 +1209,7 @@ The following operators make it easier to deal with null values. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-null-predicate-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-null-predicate-example] ---- ==== @@ -1236,7 +1238,7 @@ The expression on the right is a pattern, where: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-like-predicate-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-like-predicate-example] ---- ==== @@ -1249,7 +1251,7 @@ For example, to match all stored procedures prefixed with `Dr_`, the like criter ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-like-predicate-escape-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-like-predicate-escape-example] ---- ==== @@ -1317,7 +1319,7 @@ Even embedded attributes are allowed, although that feature depends on the level ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-in-predicate-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-in-predicate-example] ---- ==== @@ -1326,7 +1328,7 @@ include::{sourcedir}/HQLTest.java[tags=hql-in-predicate-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-collection-expressions-in-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-collection-expressions-in-example] ---- ==== @@ -1352,7 +1354,7 @@ The qualifiers are unary prefix operators: `all`, `every`, `any`, and `some`. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-all-subquery-comparison-qualifier-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-all-subquery-comparison-qualifier-example] ---- ==== @@ -1361,12 +1363,12 @@ include::{sourcedir}/HQLTest.java[tags=hql-all-subquery-comparison-qualifier-exa ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-collection-expressions-all-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-collection-expressions-all-example] ---- [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-collection-expressions-some-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-collection-expressions-some-example] ---- ==== @@ -1387,7 +1389,7 @@ As you can surely guess, `not exists` evaluates to true if the thing to the righ ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-collection-expressions-exists-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-collection-expressions-exists-example] ---- ==== @@ -1408,7 +1410,7 @@ The following operators apply to collection-valued attributes and to-many associ ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-empty-collection-predicate-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-empty-collection-predicate-example] ---- ==== @@ -1417,7 +1419,7 @@ include::{sourcedir}/HQLTest.java[tags=hql-empty-collection-predicate-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-member-of-collection-predicate-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-member-of-collection-predicate-example] ---- ==== @@ -1463,7 +1465,7 @@ Remember, the _entity name_ is the value of the `name` member of the `@Entity` a ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-select-simplest-jpql-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-simplest-jpql-example] ---- ==== @@ -1477,7 +1479,7 @@ Then Hibernate will query every entity which inherits the named type. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-select-simplest-jpql-fqn-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-simplest-jpql-fqn-example] ---- ==== @@ -1488,12 +1490,12 @@ Of course, there may be multiple root entities. ==== [source, SQL, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-multiple-root-reference-jpql-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-multiple-root-reference-jpql-example] ---- [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-multiple-same-root-reference-jpql-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-multiple-same-root-reference-jpql-example] ---- ==== @@ -1504,7 +1506,7 @@ The previous queries may even be written using the syntax `cross join` in place ==== [source, SQL, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-cross-join-jpql-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-cross-join-jpql-example] ---- ==== @@ -1518,7 +1520,7 @@ Consider: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-polymorphism-example, indent=0] +include::{example-dir-hql}/HQLTest.java[tags=hql-polymorphism-example, indent=0] ---- ==== @@ -1547,7 +1549,7 @@ It must declare an identification variable. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-derived-root-example, indent=0] +include::{example-dir-hql}/HQLTest.java[tags=hql-derived-root-example, indent=0] ---- ==== @@ -1597,7 +1599,7 @@ An explicit root join works just like an ANSI-style join in SQL. ==== [source, SQL, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-explicit-root-join-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-explicit-root-join-example] ---- ==== @@ -1627,7 +1629,7 @@ An explicit join may assign an identification variable to the joined entity. ==== [source, SQL, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-explicit-inner-join-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-explicit-inner-join-example] ---- ==== @@ -1636,7 +1638,7 @@ include::{sourcedir}/HQLTest.java[tags=hql-explicit-inner-join-example] ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-explicit-outer-join-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-explicit-outer-join-example] ---- ==== @@ -1663,7 +1665,7 @@ Join conditions occurring in the `with` or `on` clause are added to the `on` cla ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-explicit-join-with-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-explicit-join-with-example] ---- ==== @@ -1674,7 +1676,7 @@ The following query is arguably less clear, but semantically equivalent: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-explicit-join-jpql-on-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-explicit-join-jpql-on-example] ---- ==== @@ -1701,7 +1703,7 @@ For example, if `Person` has a one-to-many association named `phones`, the use o ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-explicit-fetch-join-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-explicit-fetch-join-example] ---- ==== @@ -1738,7 +1740,7 @@ An explicit join may narrow the type of the joined entity using `treat()`. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-join-treat-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-join-treat-example] ---- ==== @@ -1761,7 +1763,7 @@ The `lateral` keyword just distinguishes the two cases. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-derived-join-example, indent=0] +include::{example-dir-hql}/HQLTest.java[tags=hql-derived-join-example, indent=0] ---- ==== @@ -1808,7 +1810,7 @@ In the second case, Hibernate with automatically add a join to the generated SQL ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-implicit-join-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-implicit-join-example] ---- ==== @@ -1825,7 +1827,7 @@ Note that: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-implicit-join-alias-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-implicit-join-alias-example] ---- ==== @@ -1843,7 +1845,7 @@ When a join involves a collection or many-valued association, the declared ident ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-collection-valued-associations] +include::{example-dir-hql}/HQLTest.java[tags=hql-collection-valued-associations] ---- ==== @@ -1872,9 +1874,9 @@ In particular, `index()` and `key()` obtain a reference to a list index or map k ==== [source, JAVA, indent=0] ---- -include::{modeldir}/Phone.java[tags=hql-collection-qualification-example, indent=0] +include::{example-dir-model}/Phone.java[tags=hql-collection-qualification-example, indent=0] -include::{sourcedir}/HQLTest.java[tags=hql-collection-qualification-example, indent=0] +include::{example-dir-hql}/HQLTest.java[tags=hql-collection-qualification-example, indent=0] ---- ==== @@ -1887,7 +1889,7 @@ The functions `element()`, `index()`, `key()`, and `value()` may even be applied ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-collection-implicit-join-example, indent=0] +include::{example-dir-hql}/HQLTest.java[tags=hql-collection-implicit-join-example, indent=0] ---- ==== @@ -1898,7 +1900,7 @@ An element of an indexed collection (an array, list, or map) may even be identif ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-collection-index-operator-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-collection-index-operator-example] ---- ==== @@ -1931,7 +1933,7 @@ But if there are multiple expressions in the select list then: ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=jpql-projection-example] +include::{example-dir-hql}/HQLTest.java[tags=jpql-projection-example] ---- ==== @@ -1967,9 +1969,9 @@ The `select new` construct packages the query results into a user-written Java c ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/CallStatistics.java[tags=hql-select-clause-dynamic-instantiation-example] +include::{example-dir-hql}/CallStatistics.java[tags=hql-select-clause-dynamic-instantiation-example] -include::{sourcedir}/HQLTest.java[tags=hql-select-clause-dynamic-instantiation-example, indent=0] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-clause-dynamic-instantiation-example, indent=0] ---- ==== @@ -1989,7 +1991,7 @@ Alternatively, using the syntax `select new map`, the query may specify that eac ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-select-clause-dynamic-map-instantiation-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-clause-dynamic-map-instantiation-example] ---- ==== @@ -2003,7 +2005,7 @@ Or, using the syntax `select new list`, the query may specify that each result s ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/HQLTest.java[tags=hql-select-clause-dynamic-list-instantiation-example] +include::{example-dir-hql}/HQLTest.java[tags=hql-select-clause-dynamic-list-instantiation-example] ---- ==== @@ -2025,7 +2027,7 @@ It's only effect is to add `distinct` to the generated SQL. ==== [source, JAVA, indent=0] ---- -include::{sourcedir}/SelectDistinctTest.java[tags=hql-distinct-projection-query-example] +include::{example-dir-hql}/SelectDistinctTest.java[tags=hql-distinct-projection-query-example] ---- ==== @@ -2072,7 +2074,7 @@ There are also < Date: Fri, 27 Jan 2023 23:54:36 +0000 Subject: [PATCH 0009/1497] Pre-steps for release : `6.2.0.CR2` --- changelog.txt | 58 +++++++++++++++++++++++++++++++++++++++ gradle/version.properties | 2 +- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index ddfc8bdb5d7e..74f89e04f1fa 100644 --- a/changelog.txt +++ b/changelog.txt @@ -3,6 +3,64 @@ Hibernate 6 Changelog Note: Please refer to JIRA to learn more about each issue. +Changes in 6.2.0.CR2 (January 27, 2023) +------------------------------------------------------------------------------------------------------------------------ + +https://hibernate.atlassian.net/projects/HHH/versions/32128 + +** Bug + * [HHH-16108] - NullPointerException when flushing a (simple) entity update for models with bytecode enhancement and multiple one-to-one associations (some lazy) + * [HHH-16106] - Using BatchEntitySelectFetchInitializer causes PostLoad to be called before references are initialized + * [HHH-16099] - Log about immutable properties only if dirty on update + * [HHH-16096] - Passing an ExtendedBeanManager which is notified too late leads to initialization error + * [HHH-16077] - Added named native queries cannot specify result-class + * [HHH-16070] - Exception when find by association id that is a generic @EmbeddedId with @MappedSuperclass + * [HHH-16069] - Skip CDI for Hibernate extensions by default + * [HHH-16062] - jakarta.persistence.query.timeout not working on Hibernate 6.1.6 for Criteria Queries + * [HHH-16061] - SqmDynamicInstantiation warns about dynamic Map instantiation when using an entity + * [HHH-16049] - Setting a property to its current value with bytecode enhancement enabled results in unnecessary SQL Update in some (many) cases + * [HHH-16045] - ambiguity in grammar of HQL datetime literals + * [HHH-16043] - Hibernate 6.x breaks collection batch fetching + * [HHH-16039] - Stream fails to fetch object during processing where BatchEntitySelectFetchInitializer gets used + * [HHH-16036] - Fix Oracle CI parameter STATISTICS_LEVEL + * [HHH-16035] - Duration literals and 'by' are almost completely broken + * [HHH-16033] - Many-to-Many inverse mapping referencing the same class uses pk instead of fk field for removal + * [HHH-16031] - @ManyToMany with @JoinTable(inverseColumn = ...) and SortedSet may results in data loss + * [HHH-16025] - Using BatchEntitySelectFetchInitializer with caching leads to caching wrong values + * [HHH-16023] - ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 1 for NamedNativeQuery with generics + * [HHH-16020] - Hibernate doesn't set the correct bind parameter for the offset when using NativeQuery.setFirstResult() on Postgresql + * [HHH-16014] - Querying property from treated path causes IllegalArgumentException: Already registered a copy + * [HHH-16010] - DefaultEvictEventListener calls handleEviction with swapped parameters + * [HHH-16007] - PropertyAccessException when deleting entities with Embeddables with child collections + * [HHH-16005] - @BatchSize with @ManyToMany does not work as expected when migrating 5 -> 6 + * [HHH-16004] - Downcasting with CriteriaBuilder.treat causes ClassCastException + * [HHH-15986] - Eager Bidirectional association, initializing an HibernateProxy should associate to the circular association the HibernateProxy itself + * [HHH-15982] - Bidirectional one-to-one associations produce child entities with null parent entity references + * [HHH-15967] - @OneToOne(mappedBy = ..., fetch = LAZY) in embedded referencing an association within another embedded + * [HHH-15966] - ElementCollection with nested Embeddables fails with ArrayIndexOutOfBoundsException + * [HHH-15950] - AssertionError with bidirectional OneToOne relation using AttributeConverter for the FK + * [HHH-15934] - @Basic(optional=false) has no effect + * [HHH-15933] - broken SQL generated for @ManyToOne with @JoinColumn which references a column of a @SecondaryTable + * [HHH-15928] - Distinct with maxResults fails under SQLServer + * [HHH-15921] - @BatchSize and @IdClass on join column throws exception + * [HHH-15902] - @OneToMany relationship with @Where on child table generates wrong sql + * [HHH-15890] - springboot 3.0.0 + hibernate 6.1.5.Final + IBM DB2 error after migrating from springboot 2.7.0 + hibernate 5.6.9.Final + * [HHH-15888] - review exception reporting in Column.getSqlType()/getSqlTypeName() + * [HHH-15866] - Hibernate validation fails when OneToMany refers to fereign key in embeddable object and is marked as nullable false + * [HHH-15865] - OneToMany foreign key relation throws when id is inside nested embeddables + * [HHH-15864] - OrphanRemoval does not work with embeddables when deleting entity + * [HHH-15854] - Improve CollectionInitializer and EntityDelayedFetchInitializer resolveInstance methods performance when the parent entity is initialized + * [HHH-15851] - Mixup of entities in refresh with BatchSize + * [HHH-15839] - CriteriaBuilder treat method on Path causes ClassCastException + * [HHH-15822] - Unexpected org.hibernate.UnknownEntityTypeException: Unable to locate persister + * [HHH-15794] - NullPointerException when constructing mapping model for nested embeddables with not optional ManyToOne + * [HHH-15617] - Fix Documentation for direct fetching with Filter + * [HHH-15604] - Identically-named association in entity root and elementcollection of embeddables leads to assertion error + * [HHH-15372] - Static metamodel generator references version 2.1 + * [HHH-14526] - Problem with InheritanceType.JOINED without own subtable + * [HHH-14338] - HSQLDialect relies on "MODULE"-Prefix for local temporary table creation but MODULE-Prefix has been dropped in HSQLDB Version 2.5.1 + + Changes in 6.2.0.CR1 (December 22, 2022) ------------------------------------------------------------------------------------------------------------------------ diff --git a/gradle/version.properties b/gradle/version.properties index f0e51cefccd4..4dd4d59c6335 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=6.2.0-SNAPSHOT \ No newline at end of file +hibernateVersion=6.2.0.CR2 \ No newline at end of file From 988345951454e19d76f79968cef4b52c41f31f7b Mon Sep 17 00:00:00 2001 From: Hibernate-CI Date: Fri, 27 Jan 2023 23:59:29 +0000 Subject: [PATCH 0010/1497] Post-steps for release : `6.2.0.CR2` --- gradle/version.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/version.properties b/gradle/version.properties index 4dd4d59c6335..f0e51cefccd4 100644 --- a/gradle/version.properties +++ b/gradle/version.properties @@ -1 +1 @@ -hibernateVersion=6.2.0.CR2 \ No newline at end of file +hibernateVersion=6.2.0-SNAPSHOT \ No newline at end of file From 1b2fd1f8a2c80f5cae76d890be506cd4743408fa Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Fri, 27 Jan 2023 22:19:39 -0600 Subject: [PATCH 0011/1497] HHH-16113 - Add version checks for MERGE support to dialects --- .../java/org/hibernate/dialect/H2Dialect.java | 29 +++++++++++++++ .../hibernate/dialect/PostgreSQLDialect.java | 37 ++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) 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 f8264c188f98..82627e12c5c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/H2Dialect.java @@ -63,6 +63,7 @@ import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.internal.OptionalTableUpdate; +import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorH2DatabaseImpl; import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl; import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor; @@ -119,6 +120,8 @@ public class H2Dialect extends Dialect { private final String querySequenceString; private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this); + private final OptionalTableUpdateStrategy optionalTableUpdateStrategy; + public H2Dialect(DialectResolutionInfo info) { this( parseVersion( info ) ); registerKeywords( info ); @@ -146,6 +149,10 @@ public H2Dialect(DatabaseVersion version) { ? SequenceInformationExtractorLegacyImpl.INSTANCE : SequenceInformationExtractorH2DatabaseImpl.INSTANCE; this.querySequenceString = "select * from INFORMATION_SCHEMA.SEQUENCES"; + + this.optionalTableUpdateStrategy = version.isSameOrAfter( 1, 4, 200 ) + ? H2Dialect::usingMerge + : H2Dialect::withoutMerge; } private static DatabaseVersion parseVersion(DialectResolutionInfo info) { @@ -871,12 +878,34 @@ public int rowIdSqlType() { return BIGINT; } + @FunctionalInterface + private interface OptionalTableUpdateStrategy { + MutationOperation buildMutationOperation( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory); + } + @Override public MutationOperation createOptionalTableUpdateOperation( EntityMutationTarget mutationTarget, OptionalTableUpdate optionalTableUpdate, SessionFactoryImplementor factory) { + return optionalTableUpdateStrategy.buildMutationOperation( mutationTarget, optionalTableUpdate, factory ); + } + + private static MutationOperation usingMerge( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { final H2SqlAstTranslator translator = new H2SqlAstTranslator<>( factory, optionalTableUpdate ); return translator.createMergeOperation( optionalTableUpdate ); } + + private static MutationOperation withoutMerge( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { + return new OptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory ); + } } 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 fccf122fdd39..e01012313d64 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -71,6 +71,7 @@ import org.hibernate.sql.exec.spi.JdbcOperation; import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.internal.OptionalTableUpdate; +import org.hibernate.sql.model.jdbc.OptionalTableUpdateOperation; import org.hibernate.type.JavaObjectType; import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType; import org.hibernate.type.descriptor.jdbc.AggregateJdbcType; @@ -136,10 +137,12 @@ */ public class PostgreSQLDialect extends Dialect { private final static DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 10 ); + private static final PostgreSQLIdentityColumnSupport IDENTITY_COLUMN_SUPPORT = new PostgreSQLIdentityColumnSupport(); + private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this); private final PostgreSQLDriverKind driverKind; - private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this); + private final OptionalTableUpdateStrategy optionalTableUpdateStrategy; public PostgreSQLDialect() { this( MINIMUM_VERSION ); @@ -148,16 +151,25 @@ public PostgreSQLDialect() { public PostgreSQLDialect(DialectResolutionInfo info) { super(info); driverKind = PostgreSQLDriverKind.determineKind( info ); + optionalTableUpdateStrategy = determineOptionalTableUpdateStrategy( info ); + } + + private static OptionalTableUpdateStrategy determineOptionalTableUpdateStrategy(DatabaseVersion version) { + return version.isSameOrAfter( DatabaseVersion.make( 15, 0 ) ) + ? PostgreSQLDialect::usingMerge + : PostgreSQLDialect::withoutMerge; } public PostgreSQLDialect(DatabaseVersion version) { super(version); driverKind = PostgreSQLDriverKind.PG_JDBC; + optionalTableUpdateStrategy = determineOptionalTableUpdateStrategy( version ); } public PostgreSQLDialect(DatabaseVersion version, PostgreSQLDriverKind driverKind) { super(version); this.driverKind = driverKind; + optionalTableUpdateStrategy = determineOptionalTableUpdateStrategy( version ); } @Override @@ -1376,12 +1388,35 @@ public int rowIdSqlType() { return OTHER; } + + @FunctionalInterface + private interface OptionalTableUpdateStrategy { + MutationOperation buildMutationOperation( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory); + } + @Override public MutationOperation createOptionalTableUpdateOperation( EntityMutationTarget mutationTarget, OptionalTableUpdate optionalTableUpdate, SessionFactoryImplementor factory) { + return optionalTableUpdateStrategy.buildMutationOperation( mutationTarget, optionalTableUpdate, factory ); + } + + private static MutationOperation usingMerge( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { final PostgreSQLSqlAstTranslator translator = new PostgreSQLSqlAstTranslator<>( factory, optionalTableUpdate ); return translator.createMergeOperation( optionalTableUpdate ); } + + private static MutationOperation withoutMerge( + EntityMutationTarget mutationTarget, + OptionalTableUpdate optionalTableUpdate, + SessionFactoryImplementor factory) { + return new OptionalTableUpdateOperation( mutationTarget, optionalTableUpdate, factory ); + } } From 9a1a703fa991c430487e0b1527ada96b7836ce67 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Sun, 29 Jan 2023 15:38:34 +0800 Subject: [PATCH 0012/1497] Fix wrong @Deprecated since version --- .../java/org/hibernate/tuple/AnnotationValueGeneration.java | 2 +- .../src/main/java/org/hibernate/tuple/GenerationTiming.java | 2 +- .../src/main/java/org/hibernate/tuple/ValueGeneration.java | 2 +- .../src/main/java/org/hibernate/tuple/ValueGenerator.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/AnnotationValueGeneration.java b/hibernate-core/src/main/java/org/hibernate/tuple/AnnotationValueGeneration.java index 1c12076e044b..27096d195f4c 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/AnnotationValueGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/AnnotationValueGeneration.java @@ -34,7 +34,7 @@ * * @deprecated Replaced by {@link AnnotationBasedGenerator} */ -@Deprecated(since = "6", forRemoval = true) +@Deprecated(since = "6.2", forRemoval = true) public interface AnnotationValueGeneration extends ValueGeneration, AnnotationBasedGenerator { /** diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/GenerationTiming.java b/hibernate-core/src/main/java/org/hibernate/tuple/GenerationTiming.java index 23c2b64c8920..bf9bb4a7151b 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/GenerationTiming.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/GenerationTiming.java @@ -24,7 +24,7 @@ * redefined using the new broader {@linkplain org.hibernate.generator generation} * approach. */ -@Deprecated(since = "6", forRemoval = true) +@Deprecated(since = "6.2", forRemoval = true) public enum GenerationTiming { /** * Value generation that never occurs. diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/ValueGeneration.java b/hibernate-core/src/main/java/org/hibernate/tuple/ValueGeneration.java index 62033efc924f..cd52c9a89208 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/ValueGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/ValueGeneration.java @@ -28,7 +28,7 @@ * * @deprecated Replaced by {@link Generator} */ -@Deprecated(since = "6", forRemoval = true) +@Deprecated(since = "6.2", forRemoval = true) public interface ValueGeneration extends BeforeExecutionGenerator, OnExecutionGenerator { /** * Specifies that the property value is generated: diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/ValueGenerator.java b/hibernate-core/src/main/java/org/hibernate/tuple/ValueGenerator.java index 8d90a05100be..80ec8cb40f8a 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/ValueGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/ValueGenerator.java @@ -16,7 +16,7 @@ * * @author Steve Ebersole */ -@Deprecated(since = "6", forRemoval = true) +@Deprecated(since = "6.2", forRemoval = true) public interface ValueGenerator { /** * Generate the value. From 5e8b43edf90b26ccb5c8ab4af1a02e32eb2bb1ba Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 2 Feb 2023 18:04:21 +0100 Subject: [PATCH 0013/1497] Fix HANA test issues --- .../org/hibernate/dialect/AbstractHANADialect.java | 12 ++++++++++++ .../FieldAccessedNestedEmbeddableMetadataTest.java | 8 +++++++- .../hibernate/orm/test/type/VarbinaryArrayTest.java | 2 ++ 3 files changed, 21 insertions(+), 1 deletion(-) 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 5f7868b90696..66636f85fa71 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -1164,6 +1164,12 @@ public long getFractionalSecondPrecisionInNanos() { public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { switch (unit) { case NANOSECOND: + if ( temporalType == TemporalType.TIME ) { + return "cast(add_nano100('1970-01-01 '||(?3),?2/100) as time)"; + } + else { + return "add_nano100(?3,?2/100)"; + } case NATIVE: if ( temporalType == TemporalType.TIME ) { return "cast(add_nano100('1970-01-01 '||(?3),?2) as time)"; @@ -1188,6 +1194,12 @@ public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalType, TemporalType toTemporalType) { switch (unit) { case NANOSECOND: +// if ( temporalType == TemporalType.TIME ) { +// return "nano100_between(cast(?3 as timestamp), cast(?2 as timestamp))*100"; +// } +// else { + return "nano100_between(?2,?3)*100"; +// } case NATIVE: // if ( temporalType == TemporalType.TIME ) { // return "nano100_between(cast(?3 as timestamp), cast(?2 as timestamp))"; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/nested/fieldaccess/FieldAccessedNestedEmbeddableMetadataTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/nested/fieldaccess/FieldAccessedNestedEmbeddableMetadataTest.java index 5acc6d1c4929..e34c253a9908 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/nested/fieldaccess/FieldAccessedNestedEmbeddableMetadataTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/embeddables/nested/fieldaccess/FieldAccessedNestedEmbeddableMetadataTest.java @@ -20,6 +20,8 @@ import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.Value; import org.hibernate.type.CustomType; +import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; +import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.testing.orm.junit.FailureExpected; import org.junit.jupiter.api.Test; @@ -40,6 +42,7 @@ public void testEnumTypeInterpretation() { final Metadata metadata = new MetadataSources( ssr ) .addAnnotatedClass( Customer.class ) .buildMetadata(); + final TypeConfiguration typeConfiguration = metadata.getDatabase().getTypeConfiguration(); PersistentClass classMetadata = metadata.getEntityBinding( Customer.class.getName() ); Property investmentsProperty = classMetadata.getProperty( "investments" ); @@ -54,7 +57,10 @@ public void testEnumTypeInterpretation() { CustomType currencyType = (CustomType) currencyMetadata.getType(); int[] currencySqlTypes = currencyType.getSqlTypeCodes( metadata ); assertEquals( 1, currencySqlTypes.length ); - assertJdbcTypeCode( Types.VARCHAR, currencySqlTypes[0] ); + assertJdbcTypeCode( + typeConfiguration.getJdbcTypeRegistry().getDescriptor( Types.VARCHAR ).getJdbcTypeCode(), + currencySqlTypes[0] + ); } finally { StandardServiceRegistryBuilder.destroy( ssr ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/VarbinaryArrayTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/VarbinaryArrayTest.java index 2bbfaec5a0dc..fa170c9b7a2a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/type/VarbinaryArrayTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/VarbinaryArrayTest.java @@ -13,6 +13,7 @@ import java.util.stream.Stream; import org.hibernate.annotations.JdbcTypeCode; +import org.hibernate.dialect.AbstractHANADialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.SybaseDialect; import org.hibernate.type.SqlTypes; @@ -214,6 +215,7 @@ void queryById(String propertyName, long id, T value, Function void queryByData(String propertyName, long id, T value, Function getter, SessionFactoryScope scope) { scope.inTransaction( session -> { From 7db2dd9d60e29d1f62a5f1819766906597846699 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 30 Jan 2023 18:23:43 +0100 Subject: [PATCH 0014/1497] HHH-16003 Add test for issue --- ...eTableAliasInSubqueryWithEmbeddedTest.java | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SameTableAliasInSubqueryWithEmbeddedTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SameTableAliasInSubqueryWithEmbeddedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SameTableAliasInSubqueryWithEmbeddedTest.java new file mode 100644 index 000000000000..e7f7e09b8cc4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/SameTableAliasInSubqueryWithEmbeddedTest.java @@ -0,0 +1,228 @@ +/* + * 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.orm.test.query.hql; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TypedQuery; + +import static java.util.Objects.requireNonNull; +import static java.util.UUID.randomUUID; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @author Marco Belladelli + */ +@SessionFactory(useCollectingStatementInspector = true) +@DomainModel(annotatedClasses = SameTableAliasInSubqueryWithEmbeddedTest.MasterDataFileEntity.class) +public class SameTableAliasInSubqueryWithEmbeddedTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final MasterDataMetaData metadata1 = new MasterDataMetaData( + "SYSTEM", + "AT", + TransportMode.INTERNATIONAL, + "EUR", + "NESTED_1" + ); + final MasterDataFileEntity entity1 = new MasterDataFileEntity( + new PrimaryKey(), + metadata1, + LocalDateTime.now(), + MasterDataImportStatus.SUCCESS + ); + session.persist( entity1 ); + final MasterDataMetaData metadata2 = new MasterDataMetaData( + "PREMIUM", + "DE", + TransportMode.DOMESTIC, + "EUR", + "NESTED_2" + ); + final MasterDataFileEntity entity2 = new MasterDataFileEntity( + new PrimaryKey(), + metadata2, + LocalDateTime.now(), + MasterDataImportStatus.SUCCESS + ); + session.persist( entity2 ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> session.createMutationQuery( "delete from MasterDataFileEntity" ).executeUpdate() + ); + } + + + @Test + public void test(SessionFactoryScope scope) { + final String jpql = + "select mdf.id from MasterDataFileEntity as mdf " + + "where mdf.dataImportStatus = 'SUCCESS' " + + " and mdf.metaData.country = :countryCode " + + " and mdf.metaData.nestedEmbeddable.nestedProperty = :nested " + + " and mdf.importFinishedAt = " + + " (select max(mdf.importFinishedAt) from MasterDataFileEntity as mdf " + + " where mdf.dataImportStatus = 'SUCCESS' " + + " and mdf.metaData.country = :countryCode " + + " and mdf.metaData.nestedEmbeddable.nestedProperty = :nested)"; + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + scope.inTransaction( session -> { + TypedQuery query = session.createQuery( jpql, PrimaryKey.class ); + query.setParameter( "countryCode", "DE" ); + query.setParameter( "nested", "NESTED_2" ); + assertNotNull( query.getSingleResult() ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "m1_0", 6 ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "m2_0", 5 ); + } ); + } + + @Test + public void testNestedOnly(SessionFactoryScope scope) { + final String jpql = + "select mdf.id from MasterDataFileEntity as mdf " + + "where mdf.metaData.nestedEmbeddable.nestedProperty = :nested " + + " and mdf.importFinishedAt = " + + " (select max(mdf.importFinishedAt) from MasterDataFileEntity as mdf " + + " where mdf.metaData.nestedEmbeddable.nestedProperty = :nested)"; + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + scope.inTransaction( session -> { + TypedQuery query = session.createQuery( jpql, PrimaryKey.class ); + query.setParameter( "nested", "NESTED_2" ); + assertNotNull( query.getSingleResult() ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "m1_0", 4 ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "m2_0", 3 ); + } ); + } + + @Embeddable + public static class PrimaryKey implements Serializable { + private String value; + + public PrimaryKey() { + value = randomUUID().toString(); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public enum TransportMode { + DOMESTIC, INTERNATIONAL; + } + + @Embeddable + public static class NestedEmbeddable { + private String nestedProperty; + + public NestedEmbeddable() { + } + + public NestedEmbeddable(String nestedProperty) { + this.nestedProperty = nestedProperty; + } + } + + @Embeddable + public static class MasterDataMetaData { + private String country; + + @Enumerated(EnumType.STRING) + private TransportMode transportMode; + + private String product; + + private String currencyCode; + + @Embedded + private NestedEmbeddable nestedEmbeddable; + + protected MasterDataMetaData() { + } + + public MasterDataMetaData( + String product, + String country, + TransportMode transportMode, + String currencyCode, + String nestedProperty) { + this.product = requireNonNull( product, "Product must not be null" ); + this.country = requireNonNull( country, "Country must not be null" ); + this.transportMode = requireNonNull( transportMode, "TransportMode must not be null" ); + this.currencyCode = requireNonNull( currencyCode, "CurrencyCode must not be null" ); + this.nestedEmbeddable = new NestedEmbeddable( nestedProperty ); + } + } + + public enum MasterDataImportStatus { + CREATED, FAILED, SUCCESS; + } + + @Entity(name = "MasterDataFileEntity") + @Table(name = "MasterDataFileEntity") + public static class MasterDataFileEntity { + @Id + @AttributeOverride(name = "value", column = @Column(name = "id", nullable = false, length = 36)) + private PrimaryKey id; + + @Embedded + private MasterDataMetaData metaData; + + private LocalDateTime importFinishedAt; + + @Enumerated(EnumType.STRING) + private MasterDataImportStatus dataImportStatus; + + protected MasterDataFileEntity() { + } + + public MasterDataFileEntity( + PrimaryKey id, + MasterDataMetaData metaData, + LocalDateTime importFinishedAt, + MasterDataImportStatus dataImportStatus) { + this.id = id; + this.metaData = metaData; + this.importFinishedAt = importFinishedAt; + this.dataImportStatus = dataImportStatus; + } + + public PrimaryKey getId() { + return id; + } + } + +} From 2ee4c9685244fbdcfc7a7fbd6d200d025624838f Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 30 Jan 2023 18:23:51 +0100 Subject: [PATCH 0015/1497] HHH-16003 Create correct table group for embedded valued paths --- .../sqm/sql/BaseSqmToSqlAstConverter.java | 6 ++-- .../sql/ast/spi/FromClauseAccess.java | 11 +++++- .../ast/spi/SimpleFromClauseAccessImpl.java | 36 +++++++++++++++++++ .../sql/ast/tree/from/MappedByTableGroup.java | 5 +++ .../tree/from/StandardVirtualTableGroup.java | 5 +++ .../sql/ast/tree/from/VirtualTableGroup.java | 1 + 6 files changed, 60 insertions(+), 4 deletions(-) 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 2af562ee1896..c85e62bfe710 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 @@ -3351,7 +3351,7 @@ private X prepareReusablePath(SqmPath sqmPath, FromClauseIndex fromClause if ( sqmPath instanceof SqmEntityValuedSimplePath || sqmPath instanceof SqmEmbeddedValuedSimplePath || sqmPath instanceof SqmAnyValuedSimplePath ) { - final TableGroup existingTableGroup = fromClauseIndex.findTableGroup( sqmPath.getNavigablePath() ); + final TableGroup existingTableGroup = fromClauseIndex.findTableGroupForGetOrCreate( sqmPath.getNavigablePath() ); if ( existingTableGroup == null ) { final TableGroup createdTableGroup = createTableGroup( fromClauseIndex.getTableGroup( sqmPath.getLhs().getNavigablePath() ), @@ -3387,7 +3387,7 @@ private TableGroup prepareReusablePath( if ( parentPath == null ) { return null; } - final TableGroup tableGroup = fromClauseIndex.findTableGroup( parentPath.getNavigablePath() ); + final TableGroup tableGroup = fromClauseIndex.findTableGroupForGetOrCreate( parentPath.getNavigablePath() ); if ( tableGroup == null ) { final TableGroup parentTableGroup = prepareReusablePath( fromClauseIndex, @@ -3454,7 +3454,7 @@ private void prepareForSelection(SqmPath selectionPath) { path = selectionPath; } final FromClauseIndex fromClauseIndex = getFromClauseIndex(); - final TableGroup tableGroup = fromClauseIndex.findTableGroup( path.getNavigablePath() ); + final TableGroup tableGroup = fromClauseIndex.findTableGroupForGetOrCreate( path.getNavigablePath() ); if ( tableGroup == null ) { prepareReusablePath( path, () -> null ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/FromClauseAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/FromClauseAccess.java index 391ef8fd5a71..a31a1d93f089 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/FromClauseAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/FromClauseAccess.java @@ -33,6 +33,15 @@ public interface FromClauseAccess { */ TableGroup findTableGroup(NavigablePath navigablePath); + /** + * Find the TableGroup by the NavigablePath for the purpose of creating a + * new TableGroup if none can be found. Returns {@code null} if no TableGroup + * or parent table group is registered under that NavigablePath + */ + default TableGroup findTableGroupForGetOrCreate(NavigablePath navigablePath) { + return findTableGroup( navigablePath ); + } + /** * Get a TableGroup by the NavigablePath it is registered under. If there is * no registration, an exception is thrown. @@ -62,7 +71,7 @@ default TableGroup getTableGroup(NavigablePath navigablePath) throws SqlTreeCrea * @see #registerTableGroup */ default TableGroup resolveTableGroup(NavigablePath navigablePath, Function creator) { - TableGroup tableGroup = findTableGroup( navigablePath ); + TableGroup tableGroup = findTableGroupForGetOrCreate( navigablePath ); if ( tableGroup == null ) { tableGroup = creator.apply( navigablePath ); registerTableGroup( navigablePath, tableGroup ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java index 0c36f6be7c8a..2fb244350be2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SimpleFromClauseAccessImpl.java @@ -9,9 +9,12 @@ import java.util.HashMap; import java.util.Map; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.SqlTreeCreationLogger; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.VirtualTableGroup; import org.jboss.logging.Logger; @@ -47,6 +50,39 @@ public TableGroup findTableGroup(NavigablePath navigablePath) { return parent.findTableGroup( navigablePath ); } + @Override + public TableGroup findTableGroupForGetOrCreate(NavigablePath navigablePath) { + final TableGroup tableGroup = findTableGroup( navigablePath ); + if ( parent != null && tableGroup instanceof VirtualTableGroup && tableGroup.getModelPart() instanceof EmbeddableValuedModelPart ) { + final NavigableRole navigableRole = tableGroup.getModelPart().getNavigableRole(); + if ( navigableRole != null ) { + // Traverse up the navigable path to the point where resolving the path leads us to a regular TableGroup + NavigableRole parentRole = navigableRole.getParent(); + NavigablePath parentPath = navigablePath.getParent(); + while ( parentRole.getParent() != null ) { + parentRole = parentRole.getParent(); + parentPath = parentPath.getParent(); + } + // Only return the TableGroup if its regular parent TableGroup corresponds to the underlying one + if ( findTableGroup( parentPath ) == getUnderlyingTableGroup( (VirtualTableGroup) tableGroup ) ) { + return tableGroup; + } + else { + return null; + } + } + } + return tableGroup; + } + + private TableGroup getUnderlyingTableGroup(VirtualTableGroup virtualTableGroup) { + final TableGroup tableGroup = virtualTableGroup.getUnderlyingTableGroup(); + if ( tableGroup instanceof VirtualTableGroup ) { + return getUnderlyingTableGroup( (VirtualTableGroup) tableGroup ); + } + return tableGroup; + } + @Override public void registerTableGroup(NavigablePath navigablePath, TableGroup tableGroup) { final Logger logger = SqlTreeCreationLogger.LOGGER; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MappedByTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MappedByTableGroup.java index de76d468c093..b7a1b290fe86 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MappedByTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MappedByTableGroup.java @@ -47,6 +47,11 @@ protected TableGroup getTableGroup() { return underlyingTableGroup; } + @Override + public TableGroup getUnderlyingTableGroup() { + return underlyingTableGroup; + } + @Override public NavigablePath getNavigablePath() { return navigablePath; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardVirtualTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardVirtualTableGroup.java index 1c06dd9ff06e..a4f75c7ffe9f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardVirtualTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/StandardVirtualTableGroup.java @@ -41,6 +41,11 @@ public ModelPartContainer getExpressionType() { return getModelPart(); } + @Override + public TableGroup getUnderlyingTableGroup() { + return underlyingTableGroup; + } + @Override public boolean isFetched() { return fetched; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/VirtualTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/VirtualTableGroup.java index f705d4e1d2fb..ae1769354bdc 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/VirtualTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/VirtualTableGroup.java @@ -13,4 +13,5 @@ * @author Steve Ebersole */ public interface VirtualTableGroup extends TableGroup { + TableGroup getUnderlyingTableGroup(); } From 86b720fb1f2a7da82bb3ee0ca02a20b0ad6d16a5 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Thu, 2 Feb 2023 16:22:53 +0100 Subject: [PATCH 0016/1497] HHH-15665 - Fix and added test for issue Signed-off-by: Jan Schatteman --- ...formationExtractorMariaDBDatabaseImpl.java | 5 +- ...MariaDBExtractSequenceInformationTest.java | 94 +++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/MariaDBExtractSequenceInformationTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorMariaDBDatabaseImpl.java b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorMariaDBDatabaseImpl.java index e58f5fb19671..8016f325e29d 100644 --- a/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorMariaDBDatabaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/tool/schema/extract/internal/SequenceInformationExtractorMariaDBDatabaseImpl.java @@ -12,6 +12,7 @@ import java.util.Collections; import java.util.List; +import org.hibernate.boot.model.naming.Identifier; import org.hibernate.boot.model.relational.QualifiedSequenceName; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.tool.schema.extract.spi.ExtractionContext; @@ -28,7 +29,7 @@ public class SequenceInformationExtractorMariaDBDatabaseImpl extends SequenceInf // SQL to get metadata from individual sequence private static final String SQL_SEQUENCE_QUERY = - "SELECT '%1$s' as sequence_name, minimum_value, maximum_value, start_value, increment, cache_size FROM %1$s "; + "SELECT '%1$s' as sequence_name, minimum_value, maximum_value, start_value, increment, cache_size FROM %2$s "; private static final String UNION_ALL = "UNION ALL "; @@ -56,7 +57,7 @@ public Iterable extractMetadata(ExtractionContext extractio if ( sequenceInfoQueryBuilder.length() > 0 ) { sequenceInfoQueryBuilder.append( UNION_ALL ); } - sequenceInfoQueryBuilder.append( String.format( SQL_SEQUENCE_QUERY, sequenceName ) ); + sequenceInfoQueryBuilder.append( String.format( SQL_SEQUENCE_QUERY, sequenceName, Identifier.toIdentifier( sequenceName ) ) ); } return extractionContext.getQueryResults( sequenceInfoQueryBuilder.toString(), diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/MariaDBExtractSequenceInformationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/MariaDBExtractSequenceInformationTest.java new file mode 100644 index 000000000000..9ece2c8ac62f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/dialect/functional/MariaDBExtractSequenceInformationTest.java @@ -0,0 +1,94 @@ +package org.hibernate.orm.test.dialect.functional; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import java.util.Optional; +import java.util.stream.StreamSupport; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.MariaDBDialect; +import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorMariaDBDatabaseImpl; +import org.hibernate.tool.schema.extract.spi.ExtractionContext; +import org.hibernate.tool.schema.extract.spi.SequenceInformation; +import org.hibernate.testing.orm.junit.RequiresDialect; +import org.hibernate.testing.TestForIssue; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInAutoCommit; + +/** + * @author Jan Schatteman + */ +@RequiresDialect(value = MariaDBDialect.class) +public class MariaDBExtractSequenceInformationTest { + + private final static String hhh15665SeqName = "HHH-15665-seq"; + + private final static Map settings = Map.ofEntries( + Map.entry( AvailableSettings.URL, Environment.getProperties().getProperty( AvailableSettings.URL ) ), + Map.entry( AvailableSettings.USER, Environment.getProperties().getProperty( AvailableSettings.USER ) ), + Map.entry( AvailableSettings.PASS, Environment.getProperties().getProperty( AvailableSettings.PASS ) ) + ); + + @BeforeAll + public static void setUp() throws Exception { + doInAutoCommit( settings, "CREATE SEQUENCE IF NOT EXISTS `" + hhh15665SeqName + "`" ); + } + + @AfterAll + public static void tearDown() throws SQLException { + doInAutoCommit( settings, "DROP SEQUENCE IF EXISTS `" + hhh15665SeqName + "`" ); + } + + @Test + @TestForIssue( jiraKey = "HHH-15665" ) + public void testExtractSequenceInformationForSqlServerWithCaseSensitiveCollation() { + StandardServiceRegistry ssr = new StandardServiceRegistryBuilder().applySettings( settings ).build(); + JdbcEnvironment jdbcEnvironment = ssr.getService( JdbcEnvironment.class ); + JdbcConnectionAccess bootstrapJdbcConnectionAccess = ssr.getService( JdbcServices.class ).getBootstrapJdbcConnectionAccess(); + + try ( Connection connection = bootstrapJdbcConnectionAccess.obtainConnection() ) { + Iterable sequenceInformations = SequenceInformationExtractorMariaDBDatabaseImpl.INSTANCE.extractMetadata( + new ExtractionContext.EmptyExtractionContext() { + @Override + public Connection getJdbcConnection() { + return connection; + } + + @Override + public JdbcEnvironment getJdbcEnvironment() { + return jdbcEnvironment; + } + } ); + + Assertions.assertNotNull( sequenceInformations ); + + Optional seq = StreamSupport.stream( sequenceInformations.spliterator(), false ) + .filter( + sequence -> hhh15665SeqName.equals( sequence.getSequenceName() + .getSequenceName() + .getText() ) + ) + .findFirst(); + + Assertions.assertTrue( seq.isPresent(), hhh15665SeqName + " not found" ); + } + catch ( SQLException e ) { + Assertions.fail( "Sequence information for " + hhh15665SeqName + " was not retrieved: " + e.getMessage() ); + } + finally { + StandardServiceRegistryBuilder.destroy( ssr ); + } + } +} From 426cb4edf7916d91230be4d0f2aedf9283916e8f Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 3 Feb 2023 18:00:08 +0100 Subject: [PATCH 0017/1497] Update 'com.gradle.enterprise' to '3.12.3', 'com.gradle.common-custom-user-data-gradle-plugin' to '1.8.2', GitHub actions workflow steps setup-java, cache and upload-artifact to v3, and run dependabot on github actions versions --- .github/dependabot.yml | 4 ++++ .github/workflows/contributor-build.yml | 6 +++--- settings.gradle | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 18d4d02c5d24..ecaad86410ba 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -14,3 +14,7 @@ updates: - gradle-plugin-portal schedule: interval: "weekly" + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily diff --git a/.github/workflows/contributor-build.yml b/.github/workflows/contributor-build.yml index 78849a6b3825..fc147bce6116 100644 --- a/.github/workflows/contributor-build.yml +++ b/.github/workflows/contributor-build.yml @@ -61,7 +61,7 @@ jobs: RDBMS: ${{ matrix.rdbms }} run: ci/database-start.sh - name: Set up Java 11 - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '11' @@ -70,7 +70,7 @@ jobs: run: echo "yearmonth=$(/bin/date -u "+%Y-%m")" >> $GITHUB_OUTPUT shell: bash - name: Cache Maven local repository - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache-maven with: path: | @@ -85,7 +85,7 @@ jobs: run: ./ci/build-github.sh shell: bash - name: Upload test reports (if Gradle failed) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 if: failure() with: name: test-reports-java11-${{ matrix.rdbms }} diff --git a/settings.gradle b/settings.gradle index 7eaed8f983f2..6e6a5826f82a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -19,8 +19,8 @@ pluginManagement { plugins { id 'org.hibernate.orm.build.env-settings' - id 'com.gradle.enterprise' version '3.11.1' - id 'com.gradle.common-custom-user-data-gradle-plugin' version '1.8.1' + id 'com.gradle.enterprise' version '3.12.3' + id 'com.gradle.common-custom-user-data-gradle-plugin' version '1.8.2' } // version catalog support From f051b6dd67b0d1469b7b9d29291f200335aeb1b1 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 16 Jan 2023 11:32:25 +0100 Subject: [PATCH 0018/1497] HHH-16040 Add test for issue --- .../CharArrayToStringJoinColumnTest.java | 127 +++++++++++++ .../StringToCharArrayJoinColumnTest.java | 127 +++++++++++++ ...ArrayToStringInEmbeddedJoinColumnTest.java | 161 ++++++++++++++++ ...tringInEmbeddedMultipleJoinColumnTest.java | 174 ++++++++++++++++++ ...ngToCharArrayInEmbeddedJoinColumnTest.java | 161 ++++++++++++++++ ...ArrayInEmbeddedMultipleJoinColumnTest.java | 174 ++++++++++++++++++ 6 files changed, 924 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/CharArrayToStringJoinColumnTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/StringToCharArrayJoinColumnTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedMultipleJoinColumnTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedJoinColumnTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedMultipleJoinColumnTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/CharArrayToStringJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/CharArrayToStringJoinColumnTest.java new file mode 100644 index 000000000000..ae6db6cf1c81 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/CharArrayToStringJoinColumnTest.java @@ -0,0 +1,127 @@ +/* + * 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.orm.test.mapping.joincolumn; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + CharArrayToStringJoinColumnTest.Vehicle.class, + CharArrayToStringJoinColumnTest.VehicleInvoice.class +}) +@JiraKey("HHH-16040") +public class CharArrayToStringJoinColumnTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Vehicle vehicle = new Vehicle(); + vehicle.setId( 1L ); + vehicle.setStringProp( "2020" ); + session.persist( vehicle ); + + VehicleInvoice invoice = new VehicleInvoice(); + invoice.setId( "2020".toCharArray() ); + invoice.setVehicle( vehicle ); + session.persist( invoice ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from VehicleInvoice" ).executeUpdate(); + session.createMutationQuery( "delete from Vehicle" ).executeUpdate(); + } ); + } + + @Test + public void testAssociation(SessionFactoryScope scope) { + scope.inTransaction( session -> { + List resultList = session.createQuery( + "from VehicleInvoice", + VehicleInvoice.class + ).getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); + assertEquals( "2020", resultList.get( 0 ).getVehicle().getStringProp() ); + } ); + } + + @Entity(name = "VehicleInvoice") + public static class VehicleInvoice { + @Id + @Column(name = "char_array_col") + private char[] id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "char_array_col", referencedColumnName = "string_col", insertable = false, updatable = false) + private Vehicle vehicle; + + public char[] getId() { + return id; + } + + public void setId(char[] id) { + this.id = id; + } + + public Vehicle getVehicle() { + return vehicle; + } + + public void setVehicle(Vehicle vehicle) { + this.vehicle = vehicle; + } + } + + @Entity(name = "Vehicle") + public static class Vehicle implements Serializable { + @Id + private Long id; + + @Column(name = "string_col", nullable = false) + private String stringProp; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getStringProp() { + return stringProp; + } + + public void setStringProp(String stringProp) { + this.stringProp = stringProp; + } + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/StringToCharArrayJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/StringToCharArrayJoinColumnTest.java new file mode 100644 index 000000000000..2ac91d2d79ae --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/StringToCharArrayJoinColumnTest.java @@ -0,0 +1,127 @@ +/* + * 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.orm.test.mapping.joincolumn; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + StringToCharArrayJoinColumnTest.Vehicle.class, + StringToCharArrayJoinColumnTest.VehicleInvoice.class +}) +@JiraKey("HHH-16040") +public class StringToCharArrayJoinColumnTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Vehicle vehicle = new Vehicle(); + vehicle.setId( 1L ); + vehicle.setCharArrayProp( "2020".toCharArray() ); + session.persist( vehicle ); + + VehicleInvoice invoice = new VehicleInvoice(); + invoice.setId( "2020" ); + invoice.setVehicle( vehicle ); + session.persist( invoice ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from VehicleInvoice" ).executeUpdate(); + session.createMutationQuery( "delete from Vehicle" ).executeUpdate(); + } ); + } + + @Test + public void testAssociation(SessionFactoryScope scope) { + scope.inTransaction( session -> { + List resultList = session.createQuery( + "from VehicleInvoice", + VehicleInvoice.class + ).getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); + assertEquals( "2020", new String( resultList.get( 0 ).getVehicle().getCharArrayProp() )); + } ); + } + + @Entity(name = "VehicleInvoice") + public static class VehicleInvoice { + @Id + @Column(name = "string_col") + private String id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "string_col", referencedColumnName = "char_array_col", insertable = false, updatable = false) + private Vehicle vehicle; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Vehicle getVehicle() { + return vehicle; + } + + public void setVehicle(Vehicle vehicle) { + this.vehicle = vehicle; + } + } + + @Entity(name = "Vehicle") + public static class Vehicle implements Serializable { + @Id + private Long id; + + @Column(name = "char_array_col", nullable = false) + private char[] charArrayProp; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public char[] getCharArrayProp() { + return charArrayProp; + } + + public void setCharArrayProp(char[] charArrayProp) { + this.charArrayProp = charArrayProp; + } + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnTest.java new file mode 100644 index 000000000000..d89c25823329 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnTest.java @@ -0,0 +1,161 @@ +/* + * 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.orm.test.mapping.joincolumn.embedded; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + CharArrayToStringInEmbeddedJoinColumnTest.Vehicle.class, + CharArrayToStringInEmbeddedJoinColumnTest.VehicleInvoice.class +}) +@JiraKey("HHH-16040") +public class CharArrayToStringInEmbeddedJoinColumnTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Vehicle vehicle = new Vehicle(); + vehicle.setId( 1L ); + vehicle.setStringProp( "2020" ); + session.persist( vehicle ); + + VehicleInvoice invoice = new VehicleInvoice(); + invoice.setId( new VehicleInvoiceId( "2020".toCharArray(), 2020 ) ); + invoice.setVehicle( vehicle ); + session.persist( invoice ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from VehicleInvoice" ).executeUpdate(); + session.createMutationQuery( "delete from Vehicle" ).executeUpdate(); + } ); + } + + @Test + public void testAssociation(SessionFactoryScope scope) { + scope.inTransaction( session -> { + List resultList = session.createQuery( + "from VehicleInvoice", + VehicleInvoice.class + ).getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); + assertEquals( "2020", resultList.get( 0 ).getVehicle().getStringProp() ); + } ); + } + + @Embeddable + public static class VehicleInvoiceId implements Serializable { + @Column(name = "char_array_col") + private char[] charArrayProp; + + @Column(name = "int_col") + private int intProp; + + public VehicleInvoiceId() { + } + + public VehicleInvoiceId(char[] charArrayProp, int intProp) { + this.charArrayProp = charArrayProp; + this.intProp = intProp; + } + + public char[] getCharArrayProp() { + return charArrayProp; + } + + public void setCharArrayProp(char[] charArrayProp) { + this.charArrayProp = charArrayProp; + } + + public int getIntProp() { + return intProp; + } + + public void setIntProp(int intProp) { + this.intProp = intProp; + } + } + + @Entity(name = "VehicleInvoice") + public static class VehicleInvoice { + @EmbeddedId + private VehicleInvoiceId id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "char_array_col", referencedColumnName = "string_col", insertable = false, updatable = false) + private Vehicle vehicle; + + public VehicleInvoiceId getId() { + return id; + } + + public void setId(VehicleInvoiceId id) { + this.id = id; + } + + public Vehicle getVehicle() { + return vehicle; + } + + public void setVehicle(Vehicle vehicle) { + this.vehicle = vehicle; + } + } + + @Entity(name = "Vehicle") + public static class Vehicle implements Serializable { + @Id + private Long id; + + @Column(name = "string_col", nullable = false) + private String stringProp; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getStringProp() { + return stringProp; + } + + public void setStringProp(String stringProp) { + this.stringProp = stringProp; + } + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedMultipleJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedMultipleJoinColumnTest.java new file mode 100644 index 000000000000..a711b03ee521 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedMultipleJoinColumnTest.java @@ -0,0 +1,174 @@ +/* + * 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.orm.test.mapping.joincolumn.embedded; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + CharArrayToStringInEmbeddedMultipleJoinColumnTest.Vehicle.class, + CharArrayToStringInEmbeddedMultipleJoinColumnTest.VehicleInvoice.class +}) +@JiraKey("HHH-16040") +public class CharArrayToStringInEmbeddedMultipleJoinColumnTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Vehicle vehicle = new Vehicle(); + vehicle.setId( 1L ); + vehicle.setStringProp( "2020" ); + vehicle.setIntProp( 2020 ); + session.persist( vehicle ); + + VehicleInvoice invoice = new VehicleInvoice(); + invoice.setId( new VehicleInvoiceId( "2020".toCharArray(), 2020 ) ); + invoice.setVehicle( vehicle ); + session.persist( invoice ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from VehicleInvoice" ).executeUpdate(); + session.createMutationQuery( "delete from Vehicle" ).executeUpdate(); + } ); + } + + @Test + public void testAssociation(SessionFactoryScope scope) { + scope.inTransaction( session -> { + List resultList = session.createQuery( + "from VehicleInvoice", + VehicleInvoice.class + ).getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); + assertEquals( "2020", resultList.get( 0 ).getVehicle().getStringProp() ); + } ); + } + + @Embeddable + public static class VehicleInvoiceId implements Serializable { + @Column(name = "char_array_col") + private char[] charArrayProp; + + @Column(name = "int_col") + private int intProp; + + public VehicleInvoiceId() { + } + + public VehicleInvoiceId(char[] charArrayProp, int intProp) { + this.charArrayProp = charArrayProp; + this.intProp = intProp; + } + + public char[] getCharArrayProp() { + return charArrayProp; + } + + public void setCharArrayProp(char[] charArrayProp) { + this.charArrayProp = charArrayProp; + } + + public int getIntProp() { + return intProp; + } + + public void setIntProp(int intProp) { + this.intProp = intProp; + } + } + + @Entity(name = "VehicleInvoice") + public static class VehicleInvoice { + @EmbeddedId + private VehicleInvoiceId id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "char_array_col", referencedColumnName = "string_col", insertable = false, updatable = false) + @JoinColumn(name = "int_col", referencedColumnName = "int_col", insertable = false, updatable = false) + private Vehicle vehicle; + + public VehicleInvoiceId getId() { + return id; + } + + public void setId(VehicleInvoiceId id) { + this.id = id; + } + + public Vehicle getVehicle() { + return vehicle; + } + + public void setVehicle(Vehicle vehicle) { + this.vehicle = vehicle; + } + } + + @Entity(name = "Vehicle") + public static class Vehicle implements Serializable { + @Id + private Long id; + + @Column(name = "string_col", nullable = false) + private String stringProp; + + @Column(name = "int_col", nullable = false) + private int intProp; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getStringProp() { + return stringProp; + } + + public void setStringProp(String stringProp) { + this.stringProp = stringProp; + } + + public int getIntProp() { + return intProp; + } + + public void setIntProp(int intProp) { + this.intProp = intProp; + } + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedJoinColumnTest.java new file mode 100644 index 000000000000..6f8134851f1f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedJoinColumnTest.java @@ -0,0 +1,161 @@ +/* + * 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.orm.test.mapping.joincolumn.embedded; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + StringToCharArrayInEmbeddedJoinColumnTest.Vehicle.class, + StringToCharArrayInEmbeddedJoinColumnTest.VehicleInvoice.class +}) +@JiraKey("HHH-16040") +public class StringToCharArrayInEmbeddedJoinColumnTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Vehicle vehicle = new Vehicle(); + vehicle.setId( 1L ); + vehicle.setCharArrayProp( "2020".toCharArray() ); + session.persist( vehicle ); + + VehicleInvoice invoice = new VehicleInvoice(); + invoice.setId( new VehicleInvoiceId( "2020", 2020 ) ); + invoice.setVehicle( vehicle ); + session.persist( invoice ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from VehicleInvoice" ).executeUpdate(); + session.createMutationQuery( "delete from Vehicle" ).executeUpdate(); + } ); + } + + @Test + public void testAssociation(SessionFactoryScope scope) { + scope.inTransaction( session -> { + List resultList = session.createQuery( + "from VehicleInvoice", + VehicleInvoice.class + ).getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); + assertEquals( "2020", new String( resultList.get( 0 ).getVehicle().getCharArrayProp() ) ); + } ); + } + + @Embeddable + public static class VehicleInvoiceId implements Serializable { + @Column(name = "string_col") + private String stringProp; + + @Column(name = "int_col") + private int intProp; + + public VehicleInvoiceId() { + } + + public VehicleInvoiceId(String stringProp, int intProp) { + this.stringProp = stringProp; + this.intProp = intProp; + } + + public String getStringProp() { + return stringProp; + } + + public void setStringProp(String stringProp) { + this.stringProp = stringProp; + } + + public int getIntProp() { + return intProp; + } + + public void setIntProp(int intProp) { + this.intProp = intProp; + } + } + + @Entity(name = "VehicleInvoice") + public static class VehicleInvoice { + @EmbeddedId + private VehicleInvoiceId id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "string_col", referencedColumnName = "char_array_col", insertable = false, updatable = false) + private Vehicle vehicle; + + public VehicleInvoiceId getId() { + return id; + } + + public void setId(VehicleInvoiceId id) { + this.id = id; + } + + public Vehicle getVehicle() { + return vehicle; + } + + public void setVehicle(Vehicle vehicle) { + this.vehicle = vehicle; + } + } + + @Entity(name = "Vehicle") + public static class Vehicle implements Serializable { + @Id + private Long id; + + @Column(name = "char_array_col", nullable = false) + private char[] charArrayProp; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public char[] getCharArrayProp() { + return charArrayProp; + } + + public void setCharArrayProp(char[] charArrayProp) { + this.charArrayProp = charArrayProp; + } + } +} + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedMultipleJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedMultipleJoinColumnTest.java new file mode 100644 index 000000000000..db4afd2d878d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedMultipleJoinColumnTest.java @@ -0,0 +1,174 @@ +/* + * 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.orm.test.mapping.joincolumn.embedded; + +import java.io.Serializable; +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + StringToCharArrayInEmbeddedMultipleJoinColumnTest.Vehicle.class, + StringToCharArrayInEmbeddedMultipleJoinColumnTest.VehicleInvoice.class +}) +@JiraKey("HHH-16040") +public class StringToCharArrayInEmbeddedMultipleJoinColumnTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Vehicle vehicle = new Vehicle(); + vehicle.setId( 1L ); + vehicle.setCharArrayProp( "2020".toCharArray() ); + vehicle.setIntProp( 2020 ); + session.persist( vehicle ); + + VehicleInvoice invoice = new VehicleInvoice(); + invoice.setId( new VehicleInvoiceId( "2020", 2020 ) ); + invoice.setVehicle( vehicle ); + session.persist( invoice ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from VehicleInvoice" ).executeUpdate(); + session.createMutationQuery( "delete from Vehicle" ).executeUpdate(); + } ); + } + + @Test + public void testAssociation(SessionFactoryScope scope) { + scope.inTransaction( session -> { + List resultList = session.createQuery( + "from VehicleInvoice", + VehicleInvoice.class + ).getResultList(); + assertEquals( 1, resultList.size() ); + assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); + assertEquals( "2020", new String( resultList.get( 0 ).getVehicle().getCharArrayProp() )); + } ); + } + + @Embeddable + public static class VehicleInvoiceId implements Serializable { + @Column(name = "string_col") + private String stringProp; + + @Column(name = "int_col") + private int intProp; + + public VehicleInvoiceId() { + } + + public VehicleInvoiceId(String stringProp, int intProp) { + this.stringProp = stringProp; + this.intProp = intProp; + } + + public String getStringProp() { + return stringProp; + } + + public void setStringProp(String stringProp) { + this.stringProp = stringProp; + } + + public int getIntProp() { + return intProp; + } + + public void setIntProp(int intProp) { + this.intProp = intProp; + } + } + + @Entity(name = "VehicleInvoice") + public static class VehicleInvoice { + @EmbeddedId + private VehicleInvoiceId id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "string_col", referencedColumnName = "char_array_col", insertable = false, updatable = false) + @JoinColumn(name = "int_col", referencedColumnName = "int_col", insertable = false, updatable = false) + private Vehicle vehicle; + + public VehicleInvoiceId getId() { + return id; + } + + public void setId(VehicleInvoiceId id) { + this.id = id; + } + + public Vehicle getVehicle() { + return vehicle; + } + + public void setVehicle(Vehicle vehicle) { + this.vehicle = vehicle; + } + } + + @Entity(name = "Vehicle") + public static class Vehicle implements Serializable { + @Id + private Long id; + + @Column(name = "char_array_col", nullable = false) + private char[] charArrayProp; + + @Column(name = "int_col", nullable = false) + private int intProp; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public char[] getCharArrayProp() { + return charArrayProp; + } + + public void setCharArrayProp(char[] charArrayProp) { + this.charArrayProp = charArrayProp; + } + + public int getIntProp() { + return intProp; + } + + public void setIntProp(int intProp) { + this.intProp = intProp; + } + } +} + From 29077e67abc583ca08a44cbf76bd3bc4e5f73d31 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Fri, 27 Jan 2023 11:55:25 +0100 Subject: [PATCH 0019/1497] HHH-15916 Add test for issue --- ...ringInEmbeddedJoinColumnOrFormulaTest.java | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.java new file mode 100644 index 000000000000..63284df04208 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.java @@ -0,0 +1,162 @@ +/* + * 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.orm.test.mapping.joincolumn; + +import java.io.Serializable; + +import org.hibernate.annotations.JoinColumnOrFormula; +import org.hibernate.annotations.JoinColumnsOrFormulas; +import org.hibernate.annotations.JoinFormula; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.Vehicle.class, + CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.VehicleInvoice.class +}) +@JiraKey("HHH-15916") +public class CharArrayToStringInEmbeddedJoinColumnOrFormulaTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Vehicle vehicle = new Vehicle(); + vehicle.setId( 1L ); + vehicle.setStringProp1( "VO" ); + vehicle.setStringProp2( "2020" ); + session.persist( vehicle ); + VehicleInvoice invoice = new VehicleInvoice(); + invoice.setId( new VehicleInvoiceId( "VO".toCharArray(), "2020".toCharArray() ) ); + invoice.setVehicle( vehicle ); + session.persist( invoice ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from VehicleInvoice" ).executeUpdate(); + session.createMutationQuery( "delete from Vehicle" ).executeUpdate(); + } ); + } + + @Test + public void testAssociation(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final VehicleInvoice vehicleInvoice = session.createQuery( + "from VehicleInvoice", + VehicleInvoice.class + ).getSingleResult(); + assertEquals( 1L, vehicleInvoice.getVehicle().getId() ); + assertEquals( "VO", vehicleInvoice.getVehicle().getStringProp1() ); + assertEquals( "2020", vehicleInvoice.getVehicle().getStringProp2() ); + } ); + } + + @Embeddable + public static class VehicleInvoiceId implements Serializable { + @Column(name = "char_array_col_1") + private char[] charArrayProp1; + + @Column(name = "char_array_col_2") + private char[] charArrayProp2; + + public VehicleInvoiceId() { + } + + public VehicleInvoiceId(char[] charArrayProp1, char[] charArrayProp2) { + this.charArrayProp1 = charArrayProp1; + this.charArrayProp2 = charArrayProp2; + } + } + + @Entity(name = "VehicleInvoice") + public static class VehicleInvoice { + @EmbeddedId + private VehicleInvoiceId id; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumnsOrFormulas({ + @JoinColumnOrFormula(formula = @JoinFormula(value = "trim(char_array_col_1)", referencedColumnName = "string_col_1")), + @JoinColumnOrFormula(column = @JoinColumn(name = "char_array_col_2", referencedColumnName = "string_col_2", insertable = false, updatable = false)) + }) + private Vehicle vehicle; + + public VehicleInvoiceId getId() { + return id; + } + + public void setId(VehicleInvoiceId id) { + this.id = id; + } + + public Vehicle getVehicle() { + return vehicle; + } + + public void setVehicle(Vehicle vehicle) { + this.vehicle = vehicle; + } + } + + @Entity(name = "Vehicle") + public static class Vehicle implements Serializable { + @Id + private Long id; + + @Column(name = "string_col_1", nullable = false) + private String stringProp1; + + @Column(name = "string_col_2", nullable = false) + private String stringProp2; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getStringProp1() { + return stringProp1; + } + + public void setStringProp1(String stringProp1) { + this.stringProp1 = stringProp1; + } + + public String getStringProp2() { + return stringProp2; + } + + public void setStringProp2(String stringProp2) { + this.stringProp2 = stringProp2; + } + } +} + From 1064577687d299323e6db5a77044968e30484b39 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Wed, 25 Jan 2023 18:26:52 +0100 Subject: [PATCH 0020/1497] HHH-16040 New coercing assembler when types are different from expected --- .../internal/BasicAttributeMapping.java | 8 ++- .../BasicEntityIdentifierMappingImpl.java | 6 +- .../internal/SimpleForeignKeyDescriptor.java | 9 ++- .../sql/results/graph/basic/BasicFetch.java | 56 +++++++++++++++++-- .../sql/results/graph/basic/BasicResult.java | 33 ++++++++++- .../graph/basic/BasicResultAssembler.java | 4 +- .../graph/basic/CoercingResultAssembler.java | 38 +++++++++++++ .../java/PrimitiveCharacterArrayJavaType.java | 5 ++ .../type/descriptor/java/StringJavaType.java | 7 +++ .../orm/test/jpa/criteria/TreatPathTest.java | 30 +++++----- .../CharArrayToStringJoinColumnTest.java | 40 +++++++++++-- .../StringToCharArrayJoinColumnTest.java | 41 ++++++++++++-- ...ringInEmbeddedJoinColumnOrFormulaTest.java | 4 +- ...ArrayToStringInEmbeddedJoinColumnTest.java | 40 +++++++++++-- ...tringInEmbeddedMultipleJoinColumnTest.java | 40 +++++++++++-- ...ngToCharArrayInEmbeddedJoinColumnTest.java | 40 +++++++++++-- ...ArrayInEmbeddedMultipleJoinColumnTest.java | 40 +++++++++++-- 17 files changed, 375 insertions(+), 66 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java index 2f56b9ec4bf5..545d84b32c44 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java @@ -360,6 +360,7 @@ public Fetch generateFetch( // Lazy property. A valuesArrayPosition of -1 will lead to // returning a domain result assembler that returns LazyPropertyInitializer.UNFETCHED_PROPERTY final EntityMappingType containingEntityMapping = findContainingEntityMapping(); + boolean coerceResultType = false; if ( fetchTiming == FetchTiming.DELAYED && !( fetchParent instanceof EmbeddableResultGraphNode ) && containingEntityMapping.getEntityPersister().getPropertyLaziness()[getStateArrayPosition()] ) { @@ -375,6 +376,10 @@ public Fetch generateFetch( final SqlSelection sqlSelection = resolveSqlSelection( fetchablePath, tableGroup, true, fetchParent, creationState ); valuesArrayPosition = sqlSelection.getValuesArrayPosition(); + if ( sqlSelection.getExpressionType() != null) { + // if the expression type is different that the expected type coerce the value + coerceResultType = sqlSelection.getExpressionType().getSingleJdbcMapping().getJdbcJavaType() != getJdbcMapping().getJdbcJavaType(); + } } return new BasicFetch<>( @@ -383,7 +388,8 @@ public Fetch generateFetch( fetchablePath, this, fetchTiming, - creationState + creationState, + coerceResultType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java index 3fac7df21be3..0eaef6effe60 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java @@ -22,6 +22,7 @@ import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.model.domain.NavigableRole; @@ -399,13 +400,16 @@ public Fetch generateFetch( assert tableGroup != null; final SqlSelection sqlSelection = resolveSqlSelection( fetchablePath, tableGroup, false, fetchParent, creationState ); + final JdbcMappingContainer selectionType = sqlSelection.getExpressionType(); return new BasicFetch<>( sqlSelection.getValuesArrayPosition(), fetchParent, fetchablePath, this, FetchTiming.IMMEDIATE, - creationState + creationState, + // if the expression type is different that the expected type coerce the value + selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != getJdbcMapping().getJdbcJavaType() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java index 9f49802cb2c9..73744c492bfd 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java @@ -22,6 +22,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.JdbcMappingContainer; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.MappingType; import org.hibernate.metamodel.mapping.ModelPart; @@ -316,17 +317,21 @@ private DomainResult createDomainResult( ); } + final JavaType javaType = selectableMapping.getJdbcMapping().getJdbcJavaType(); final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection( sqlExpressionResolver.resolveSqlExpression( tableReference, selectableMapping ), - selectableMapping.getJdbcMapping().getJdbcJavaType(), + javaType, fetchParent, sqlAstCreationState.getCreationContext().getSessionFactory().getTypeConfiguration() ); + final JdbcMappingContainer selectionType = sqlSelection.getExpressionType(); return new BasicResult<>( sqlSelection.getValuesArrayPosition(), null, - selectableMapping.getJdbcMapping() + selectableMapping.getJdbcMapping(), + // if the expression type is different that the expected type coerce the value + selectionType != null && selectionType.getSingleJdbcMapping().getJdbcJavaType() != javaType ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicFetch.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicFetch.java index eca9b3c3fe66..ef5f747f84ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicFetch.java @@ -55,6 +55,28 @@ public BasicFetch( ); } + public BasicFetch( + int valuesArrayPosition, + FetchParent fetchParent, + NavigablePath fetchablePath, + BasicValuedModelPart valuedMapping, + FetchTiming fetchTiming, + DomainResultCreationState creationState, + boolean coerceResultType) { + //noinspection unchecked + this( + valuesArrayPosition, + fetchParent, + fetchablePath, + valuedMapping, + (BasicValueConverter) valuedMapping.getJdbcMapping().getValueConverter(), + fetchTiming, + true, + creationState, + coerceResultType + ); + } + public BasicFetch( int valuesArrayPosition, FetchParent fetchParent, @@ -84,6 +106,29 @@ public BasicFetch( FetchTiming fetchTiming, boolean canBasicPartFetchBeDelayed, DomainResultCreationState creationState) { + this( + valuesArrayPosition, + fetchParent, + fetchablePath, + valuedMapping, + valueConverter, + fetchTiming, + canBasicPartFetchBeDelayed, + creationState, + false + ); + } + + public BasicFetch( + int valuesArrayPosition, + FetchParent fetchParent, + NavigablePath fetchablePath, + BasicValuedModelPart valuedMapping, + BasicValueConverter valueConverter, + FetchTiming fetchTiming, + boolean canBasicPartFetchBeDelayed, + DomainResultCreationState creationState, + boolean coerceResultType) { this.navigablePath = fetchablePath; this.fetchParent = fetchParent; @@ -100,11 +145,12 @@ public BasicFetch( } } else { - this.assembler = new BasicResultAssembler<>( - valuesArrayPosition, - javaType, - valueConverter - ); + if (coerceResultType) { + this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter ); + } + else { + this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter ); + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResult.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResult.java index 46cfe4116656..7329d8520c7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResult.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResult.java @@ -41,6 +41,22 @@ public BasicResult( ); } + public BasicResult( + int jdbcValuesArrayPosition, + String resultVariable, + JdbcMapping jdbcMapping, + boolean coerceResultType) { + //noinspection unchecked + this( + jdbcValuesArrayPosition, + resultVariable, + jdbcMapping.getJavaTypeDescriptor(), + jdbcMapping.getValueConverter(), + null, + coerceResultType + ); + } + public BasicResult( int jdbcValuesArrayPosition, String resultVariable, @@ -85,11 +101,26 @@ public BasicResult( JavaType javaType, BasicValueConverter valueConverter, NavigablePath navigablePath) { + this( valuesArrayPosition, resultVariable, javaType, valueConverter, navigablePath, false ); + } + + public BasicResult( + int valuesArrayPosition, + String resultVariable, + JavaType javaType, + BasicValueConverter valueConverter, + NavigablePath navigablePath, + boolean coerceResultType) { this.resultVariable = resultVariable; this.javaType = javaType; this.navigablePath = navigablePath; - this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter ); + if ( coerceResultType ) { + this.assembler = new CoercingResultAssembler<>( valuesArrayPosition, javaType, valueConverter ); + } + else { + this.assembler = new BasicResultAssembler<>( valuesArrayPosition, javaType, valueConverter ); + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java index 75c707ca02af..06a8d94806ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/BasicResultAssembler.java @@ -26,8 +26,8 @@ public static BasicResultAssembler from(SqlSelection selection, JavaType< return new BasicResultAssembler<>( selection.getValuesArrayPosition(), javaType ); } - private final int valuesArrayPosition; - private final JavaType assembledJavaType; + protected final int valuesArrayPosition; + protected final JavaType assembledJavaType; private final BasicValueConverter valueConverter; public BasicResultAssembler( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java new file mode 100644 index 000000000000..810355cd66a4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/basic/CoercingResultAssembler.java @@ -0,0 +1,38 @@ +/* + * 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.results.graph.basic; + +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; +import org.hibernate.type.descriptor.java.JavaType; + +/** + * A {@link BasicResultAssembler} which does type coercion to handle cases + * where the expression type and the expected result {@link JavaType} are different + * (e.g. same column mapped with differently typed properties). + * + * @author Marco Belladelli + */ +public class CoercingResultAssembler extends BasicResultAssembler { + public CoercingResultAssembler( + int valuesArrayPosition, + JavaType assembledJavaType, + BasicValueConverter valueConverter) { + super( valuesArrayPosition, assembledJavaType, valueConverter ); + } + + /** + * Access to the row value, coerced to expected type + */ + @Override + public Object extractRawValue(RowProcessingState rowProcessingState) { + return assembledJavaType.coerce( + rowProcessingState.getJdbcValue( valuesArrayPosition ), + rowProcessingState.getSession() + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java index 89f849b68241..2f09bdb59c20 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/PrimitiveCharacterArrayJavaType.java @@ -92,4 +92,9 @@ public char[] wrap(X value, WrapperOptions options) { } throw unknownWrap( value.getClass() ); } + + @Override + public char[] coerce(X value, CoercionContext coercionContext) { + return wrap( value, null ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringJavaType.java index cd75777494d9..517d2a9bcf39 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/StringJavaType.java @@ -90,6 +90,9 @@ public String wrap(X value, WrapperOptions options) { if (value instanceof String) { return (String) value; } + if (value instanceof char[]) { + return new String( (char[]) value ); + } if (value instanceof Reader) { return DataHelper.extractString( (Reader) value ); } @@ -113,4 +116,8 @@ public boolean isWider(JavaType javaType) { } } + @Override + public String coerce(X value, CoercionContext coercionContext) { + return wrap( value, null ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatPathTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatPathTest.java index dbc460d46adf..e64f608a0834 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatPathTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatPathTest.java @@ -42,6 +42,7 @@ import jakarta.persistence.criteria.Root; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * @author Marco Belladelli @@ -65,7 +66,7 @@ public void setUp(EntityManagerFactoryScope scope) { term.setLength( 4 ); term.setLanguage( language ); term.setAnyProperty( stringProperty ); - term.setSynonyms( new ArrayList<>( List.of( "ciao" ) ) ); + term.setSynonyms( new ArrayList<>() ); term.setEmbeddableProperty( new EmbeddableType( "ciao" ) ); Linkage linkage = new Linkage(); linkage.setTerm( term ); @@ -101,14 +102,7 @@ public void testTreatEntityValue(EntityManagerFactoryScope scope) { @Test public void testTreatPluralValue(EntityManagerFactoryScope scope) { - scope.inTransaction( entityManager -> { - try { - testCriteriaTreat( entityManager, "synonyms", List.of( "ciao" ) ); - } - catch (Exception e) { - assertEquals( UnsupportedOperationException.class, e.getClass() ); - } - } ); + scope.inTransaction( entityManager -> testCriteriaTreat( entityManager, "synonyms", null, true ) ); } @Test @@ -129,13 +123,17 @@ public void testTreatAnyValue(EntityManagerFactoryScope scope) { } private void testCriteriaTreat(EntityManager entityManager, String property, Object value) { - CriteriaBuilder cb = entityManager.getCriteriaBuilder(); - CriteriaQuery criteria = cb.createQuery( Linkage.class ); - Root root = criteria.from( Linkage.class ); - Path asLocalTerm = cb.treat( root.get( "term" ), LocalTerm.class ); - Predicate predicate; - if ( value instanceof Collection ) { - predicate = asLocalTerm.get( property ).in( value ); + testCriteriaTreat( entityManager, property, value, false ); + } + + private void testCriteriaTreat(EntityManager entityManager, String property, Object value, boolean plural) { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery criteria = cb.createQuery( Linkage.class ); + final Root root = criteria.from( Linkage.class ); + final Path asLocalTerm = cb.treat( root.get( "term" ), LocalTerm.class ); + final Predicate predicate; + if ( plural ) { + predicate = cb.isEmpty( asLocalTerm.get( property ) ); } else { predicate = cb.equal( asLocalTerm.get( property ), value ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/CharArrayToStringJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/CharArrayToStringJoinColumnTest.java index ae6db6cf1c81..1866a7183142 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/CharArrayToStringJoinColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/CharArrayToStringJoinColumnTest.java @@ -7,6 +7,7 @@ package org.hibernate.orm.test.mapping.joincolumn; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import org.hibernate.testing.orm.junit.DomainModel; @@ -23,6 +24,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -43,10 +45,10 @@ public void setUp(SessionFactoryScope scope) { vehicle.setId( 1L ); vehicle.setStringProp( "2020" ); session.persist( vehicle ); - VehicleInvoice invoice = new VehicleInvoice(); invoice.setId( "2020".toCharArray() ); invoice.setVehicle( vehicle ); + vehicle.getInvoices().add( invoice ); session.persist( invoice ); } ); } @@ -62,13 +64,24 @@ public void tearDown(SessionFactoryScope scope) { @Test public void testAssociation(SessionFactoryScope scope) { scope.inTransaction( session -> { - List resultList = session.createQuery( + final VehicleInvoice vehicleInvoice = session.createQuery( "from VehicleInvoice", VehicleInvoice.class - ).getResultList(); - assertEquals( 1, resultList.size() ); - assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); - assertEquals( "2020", resultList.get( 0 ).getVehicle().getStringProp() ); + ).getSingleResult(); + assertEquals( 1L, vehicleInvoice.getVehicle().getId() ); + assertEquals( "2020", vehicleInvoice.getVehicle().getStringProp() ); + } ); + } + + @Test + public void testInverse(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Vehicle vehicle = session.createQuery( + "from Vehicle", + Vehicle.class + ).getSingleResult(); + assertEquals( 1, vehicle.getInvoices().size() ); + assertEquals( "2020", new String( vehicle.getInvoices().get( 0 ).getId() ) ); } ); } @@ -107,6 +120,13 @@ public static class Vehicle implements Serializable { @Column(name = "string_col", nullable = false) private String stringProp; + @OneToMany(mappedBy = "vehicle") + private List invoices; + + public Vehicle() { + this.invoices = new ArrayList<>(); + } + public Long getId() { return id; } @@ -122,6 +142,14 @@ public String getStringProp() { public void setStringProp(String stringProp) { this.stringProp = stringProp; } + + public List getInvoices() { + return invoices; + } + + public void setInvoices(List invoices) { + this.invoices = invoices; + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/StringToCharArrayJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/StringToCharArrayJoinColumnTest.java index 2ac91d2d79ae..7f6a58e9c72b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/StringToCharArrayJoinColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/StringToCharArrayJoinColumnTest.java @@ -7,6 +7,7 @@ package org.hibernate.orm.test.mapping.joincolumn; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import org.hibernate.testing.orm.junit.DomainModel; @@ -23,6 +24,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -43,10 +45,10 @@ public void setUp(SessionFactoryScope scope) { vehicle.setId( 1L ); vehicle.setCharArrayProp( "2020".toCharArray() ); session.persist( vehicle ); - VehicleInvoice invoice = new VehicleInvoice(); invoice.setId( "2020" ); invoice.setVehicle( vehicle ); + vehicle.getInvoices().add( invoice ); session.persist( invoice ); } ); } @@ -62,16 +64,28 @@ public void tearDown(SessionFactoryScope scope) { @Test public void testAssociation(SessionFactoryScope scope) { scope.inTransaction( session -> { - List resultList = session.createQuery( + final VehicleInvoice vehicleInvoice = session.createQuery( "from VehicleInvoice", VehicleInvoice.class - ).getResultList(); - assertEquals( 1, resultList.size() ); - assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); - assertEquals( "2020", new String( resultList.get( 0 ).getVehicle().getCharArrayProp() )); + ).getSingleResult(); + assertEquals( 1L, vehicleInvoice.getVehicle().getId() ); + assertEquals( "2020", new String( vehicleInvoice.getVehicle().getCharArrayProp() ) ); } ); } + @Test + public void testInverse(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Vehicle vehicle = session.createQuery( + "from Vehicle", + Vehicle.class + ).getSingleResult(); + assertEquals( 1, vehicle.getInvoices().size() ); + assertEquals( "2020", vehicle.getInvoices().get( 0 ).getId() ); + } ); + } + + @Entity(name = "VehicleInvoice") public static class VehicleInvoice { @Id @@ -107,6 +121,13 @@ public static class Vehicle implements Serializable { @Column(name = "char_array_col", nullable = false) private char[] charArrayProp; + @OneToMany(mappedBy = "vehicle") + private List invoices; + + public Vehicle() { + this.invoices = new ArrayList<>(); + } + public Long getId() { return id; } @@ -122,6 +143,14 @@ public char[] getCharArrayProp() { public void setCharArrayProp(char[] charArrayProp) { this.charArrayProp = charArrayProp; } + + public List getInvoices() { + return invoices; + } + + public void setInvoices(List invoices) { + this.invoices = invoices; + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.java index 63284df04208..b03bacd61336 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnOrFormulaTest.java @@ -4,7 +4,7 @@ * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html */ -package org.hibernate.orm.test.mapping.joincolumn; +package org.hibernate.orm.test.mapping.joincolumn.embedded; import java.io.Serializable; @@ -101,7 +101,7 @@ public static class VehicleInvoice { @ManyToOne(fetch = FetchType.EAGER) @JoinColumnsOrFormulas({ - @JoinColumnOrFormula(formula = @JoinFormula(value = "trim(char_array_col_1)", referencedColumnName = "string_col_1")), + @JoinColumnOrFormula(formula = @JoinFormula(value = "char_array_col_1", referencedColumnName = "string_col_1")), @JoinColumnOrFormula(column = @JoinColumn(name = "char_array_col_2", referencedColumnName = "string_col_2", insertable = false, updatable = false)) }) private Vehicle vehicle; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnTest.java index d89c25823329..d649be48ca42 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedJoinColumnTest.java @@ -7,6 +7,7 @@ package org.hibernate.orm.test.mapping.joincolumn.embedded; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import org.hibernate.testing.orm.junit.DomainModel; @@ -25,6 +26,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -45,10 +47,10 @@ public void setUp(SessionFactoryScope scope) { vehicle.setId( 1L ); vehicle.setStringProp( "2020" ); session.persist( vehicle ); - VehicleInvoice invoice = new VehicleInvoice(); invoice.setId( new VehicleInvoiceId( "2020".toCharArray(), 2020 ) ); invoice.setVehicle( vehicle ); + vehicle.getInvoices().add( invoice ); session.persist( invoice ); } ); } @@ -64,13 +66,24 @@ public void tearDown(SessionFactoryScope scope) { @Test public void testAssociation(SessionFactoryScope scope) { scope.inTransaction( session -> { - List resultList = session.createQuery( + final VehicleInvoice vehicleInvoice = session.createQuery( "from VehicleInvoice", VehicleInvoice.class - ).getResultList(); - assertEquals( 1, resultList.size() ); - assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); - assertEquals( "2020", resultList.get( 0 ).getVehicle().getStringProp() ); + ).getSingleResult(); + assertEquals( 1L, vehicleInvoice.getVehicle().getId() ); + assertEquals( "2020", vehicleInvoice.getVehicle().getStringProp() ); + } ); + } + + @Test + public void testInverse(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Vehicle vehicle = session.createQuery( + "from Vehicle", + Vehicle.class + ).getSingleResult(); + assertEquals( 1, vehicle.getInvoices().size() ); + assertEquals( "2020", new String( vehicle.getInvoices().get( 0 ).getId().getCharArrayProp() ) ); } ); } @@ -141,6 +154,13 @@ public static class Vehicle implements Serializable { @Column(name = "string_col", nullable = false) private String stringProp; + @OneToMany(mappedBy = "vehicle") + private List invoices; + + public Vehicle() { + this.invoices = new ArrayList<>(); + } + public Long getId() { return id; } @@ -156,6 +176,14 @@ public String getStringProp() { public void setStringProp(String stringProp) { this.stringProp = stringProp; } + + public List getInvoices() { + return invoices; + } + + public void setInvoices(List invoices) { + this.invoices = invoices; + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedMultipleJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedMultipleJoinColumnTest.java index a711b03ee521..837c9153c11c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedMultipleJoinColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/CharArrayToStringInEmbeddedMultipleJoinColumnTest.java @@ -7,6 +7,7 @@ package org.hibernate.orm.test.mapping.joincolumn.embedded; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import org.hibernate.testing.orm.junit.DomainModel; @@ -25,6 +26,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -46,10 +48,10 @@ public void setUp(SessionFactoryScope scope) { vehicle.setStringProp( "2020" ); vehicle.setIntProp( 2020 ); session.persist( vehicle ); - VehicleInvoice invoice = new VehicleInvoice(); invoice.setId( new VehicleInvoiceId( "2020".toCharArray(), 2020 ) ); invoice.setVehicle( vehicle ); + vehicle.getInvoices().add( invoice ); session.persist( invoice ); } ); } @@ -65,13 +67,24 @@ public void tearDown(SessionFactoryScope scope) { @Test public void testAssociation(SessionFactoryScope scope) { scope.inTransaction( session -> { - List resultList = session.createQuery( + final VehicleInvoice vehicleInvoice = session.createQuery( "from VehicleInvoice", VehicleInvoice.class - ).getResultList(); - assertEquals( 1, resultList.size() ); - assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); - assertEquals( "2020", resultList.get( 0 ).getVehicle().getStringProp() ); + ).getSingleResult(); + assertEquals( 1L, vehicleInvoice.getVehicle().getId() ); + assertEquals( "2020", vehicleInvoice.getVehicle().getStringProp() ); + } ); + } + + @Test + public void testInverse(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Vehicle vehicle = session.createQuery( + "from Vehicle", + Vehicle.class + ).getSingleResult(); + assertEquals( 1, vehicle.getInvoices().size() ); + assertEquals( "2020", new String( vehicle.getInvoices().get( 0 ).getId().getCharArrayProp() ) ); } ); } @@ -146,6 +159,13 @@ public static class Vehicle implements Serializable { @Column(name = "int_col", nullable = false) private int intProp; + @OneToMany(mappedBy = "vehicle") + private List invoices; + + public Vehicle() { + this.invoices = new ArrayList<>(); + } + public Long getId() { return id; } @@ -169,6 +189,14 @@ public int getIntProp() { public void setIntProp(int intProp) { this.intProp = intProp; } + + public List getInvoices() { + return invoices; + } + + public void setInvoices(List invoices) { + this.invoices = invoices; + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedJoinColumnTest.java index 6f8134851f1f..8d2ed27cd972 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedJoinColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedJoinColumnTest.java @@ -7,6 +7,7 @@ package org.hibernate.orm.test.mapping.joincolumn.embedded; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import org.hibernate.testing.orm.junit.DomainModel; @@ -25,6 +26,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -45,10 +47,10 @@ public void setUp(SessionFactoryScope scope) { vehicle.setId( 1L ); vehicle.setCharArrayProp( "2020".toCharArray() ); session.persist( vehicle ); - VehicleInvoice invoice = new VehicleInvoice(); invoice.setId( new VehicleInvoiceId( "2020", 2020 ) ); invoice.setVehicle( vehicle ); + vehicle.getInvoices().add( invoice ); session.persist( invoice ); } ); } @@ -64,13 +66,24 @@ public void tearDown(SessionFactoryScope scope) { @Test public void testAssociation(SessionFactoryScope scope) { scope.inTransaction( session -> { - List resultList = session.createQuery( + final VehicleInvoice vehicleInvoice = session.createQuery( "from VehicleInvoice", VehicleInvoice.class - ).getResultList(); - assertEquals( 1, resultList.size() ); - assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); - assertEquals( "2020", new String( resultList.get( 0 ).getVehicle().getCharArrayProp() ) ); + ).getSingleResult(); + assertEquals( 1L, vehicleInvoice.getVehicle().getId() ); + assertEquals( "2020", new String( vehicleInvoice.getVehicle().getCharArrayProp() ) ); + } ); + } + + @Test + public void testInverse(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Vehicle vehicle = session.createQuery( + "from Vehicle", + Vehicle.class + ).getSingleResult(); + assertEquals( 1, vehicle.getInvoices().size() ); + assertEquals( "2020", vehicle.getInvoices().get( 0 ).getId().getStringProp() ); } ); } @@ -141,6 +154,13 @@ public static class Vehicle implements Serializable { @Column(name = "char_array_col", nullable = false) private char[] charArrayProp; + @OneToMany(mappedBy = "vehicle") + private List invoices; + + public Vehicle() { + this.invoices = new ArrayList<>(); + } + public Long getId() { return id; } @@ -156,6 +176,14 @@ public char[] getCharArrayProp() { public void setCharArrayProp(char[] charArrayProp) { this.charArrayProp = charArrayProp; } + + public List getInvoices() { + return invoices; + } + + public void setInvoices(List invoices) { + this.invoices = invoices; + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedMultipleJoinColumnTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedMultipleJoinColumnTest.java index db4afd2d878d..91733df566de 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedMultipleJoinColumnTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/joincolumn/embedded/StringToCharArrayInEmbeddedMultipleJoinColumnTest.java @@ -7,6 +7,7 @@ package org.hibernate.orm.test.mapping.joincolumn.embedded; import java.io.Serializable; +import java.util.ArrayList; import java.util.List; import org.hibernate.testing.orm.junit.DomainModel; @@ -25,6 +26,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -46,10 +48,10 @@ public void setUp(SessionFactoryScope scope) { vehicle.setCharArrayProp( "2020".toCharArray() ); vehicle.setIntProp( 2020 ); session.persist( vehicle ); - VehicleInvoice invoice = new VehicleInvoice(); invoice.setId( new VehicleInvoiceId( "2020", 2020 ) ); invoice.setVehicle( vehicle ); + vehicle.getInvoices().add( invoice ); session.persist( invoice ); } ); } @@ -65,13 +67,24 @@ public void tearDown(SessionFactoryScope scope) { @Test public void testAssociation(SessionFactoryScope scope) { scope.inTransaction( session -> { - List resultList = session.createQuery( + final VehicleInvoice vehicleInvoice = session.createQuery( "from VehicleInvoice", VehicleInvoice.class - ).getResultList(); - assertEquals( 1, resultList.size() ); - assertEquals( 1L, resultList.get( 0 ).getVehicle().getId() ); - assertEquals( "2020", new String( resultList.get( 0 ).getVehicle().getCharArrayProp() )); + ).getSingleResult(); + assertEquals( 1L, vehicleInvoice.getVehicle().getId() ); + assertEquals( "2020", new String( vehicleInvoice.getVehicle().getCharArrayProp() ) ); + } ); + } + + @Test + public void testInverse(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Vehicle vehicle = session.createQuery( + "from Vehicle", + Vehicle.class + ).getSingleResult(); + assertEquals( 1, vehicle.getInvoices().size() ); + assertEquals( "2020", vehicle.getInvoices().get( 0 ).getId().getStringProp() ); } ); } @@ -146,6 +159,13 @@ public static class Vehicle implements Serializable { @Column(name = "int_col", nullable = false) private int intProp; + @OneToMany(mappedBy = "vehicle") + private List invoices; + + public Vehicle() { + this.invoices = new ArrayList<>(); + } + public Long getId() { return id; } @@ -169,6 +189,14 @@ public int getIntProp() { public void setIntProp(int intProp) { this.intProp = intProp; } + + public List getInvoices() { + return invoices; + } + + public void setInvoices(List invoices) { + this.invoices = invoices; + } } } From 45c41fa4dd7c658ab954147ae4451ec2b62f54dd Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 30 Jan 2023 10:33:31 +0100 Subject: [PATCH 0021/1497] HHH-15998 Add test for issue --- ...ationWithJoinAndGroupAndOrderByByTest.java | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/DynamicInstantiationWithJoinAndGroupAndOrderByByTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/DynamicInstantiationWithJoinAndGroupAndOrderByByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/DynamicInstantiationWithJoinAndGroupAndOrderByByTest.java new file mode 100644 index 000000000000..cb5e1bf63426 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/DynamicInstantiationWithJoinAndGroupAndOrderByByTest.java @@ -0,0 +1,165 @@ +/* + * 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.orm.test.query.hql.instantiation; + +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.TypedQuery; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + DynamicInstantiationWithJoinAndGroupAndOrderByByTest.Item.class, + DynamicInstantiationWithJoinAndGroupAndOrderByByTest.ItemSale.class +}) +@JiraKey("HHH-15998") +public class DynamicInstantiationWithJoinAndGroupAndOrderByByTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + Item item1 = new Item(); + item1.setName( "Item 1" ); + session.persist( item1 ); + + Item item2 = new Item(); + item2.setName( "Item 2" ); + session.persist( item2 ); + + ItemSale itemSale11 = new ItemSale(); + itemSale11.setItem( item1 ); + itemSale11.setTotal( 1d ); + session.persist( itemSale11 ); + + ItemSale itemSale12 = new ItemSale(); + itemSale12.setItem( item1 ); + itemSale12.setTotal( 2d ); + session.persist( itemSale12 ); + + ItemSale itemSale21 = new ItemSale(); + itemSale21.setItem( item2 ); + itemSale21.setTotal( 5d ); + session.persist( itemSale21 ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from ItemSale" ).executeUpdate(); + session.createMutationQuery( "delete from Item" ).executeUpdate(); + } ); + } + + @Test + public void testInstantiationGroupByAndOrderBy(SessionFactoryScope scope) { + scope.inTransaction( session -> { + TypedQuery query = session.createQuery( + "select new " + getClass().getName() + "$Summary(i, sum(is.total))" + + " from ItemSale is" + + " join is.item i" + + " group by i" + + " order by i" + , + Summary.class + ); + List resultList = query.getResultList(); + assertEquals( 2, resultList.size() ); + assertEquals( "Item 1", resultList.get( 0 ).getItem().getName() ); + assertEquals( 3d, resultList.get( 0 ).getTotal() ); + assertEquals( "Item 2", resultList.get( 1 ).getItem().getName() ); + assertEquals( 5d, resultList.get( 1 ).getTotal() ); + } ); + } + + @Entity(name = "Item") + public static class Item { + @Id + @GeneratedValue + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "ItemSale") + public static class ItemSale { + @Id + @GeneratedValue + private Long id; + + @ManyToOne(optional = false) + private Item item; + + private Double total; + + public Long getId() { + return id; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + + public Double getTotal() { + return total; + } + + public void setTotal(Double total) { + this.total = total; + } + } + + public static class Summary { + private final Item item; + + private final Double total; + + private Summary(Item item, Double total) { + this.item = item; + this.total = total; + } + + public Item getItem() { + return item; + } + + public Double getTotal() { + return total; + } + } +} From 56f2973519d8c2ec40ea6b53e02b8327b3239cbc Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 30 Jan 2023 11:29:46 +0100 Subject: [PATCH 0022/1497] HHH-15991 Add test for issue --- ...ionWithJoinAndGroupByAndParameterTest.java | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/DynamicInstantiationWithJoinAndGroupByAndParameterTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/DynamicInstantiationWithJoinAndGroupByAndParameterTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/DynamicInstantiationWithJoinAndGroupByAndParameterTest.java new file mode 100644 index 000000000000..0977e3ffc3a1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/hql/instantiation/DynamicInstantiationWithJoinAndGroupByAndParameterTest.java @@ -0,0 +1,187 @@ +/* + * 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.orm.test.query.hql.instantiation; + +import java.util.List; + +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.TypedQuery; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@Jpa(annotatedClasses = { + DynamicInstantiationWithJoinAndGroupByAndParameterTest.Action.class, + DynamicInstantiationWithJoinAndGroupByAndParameterTest.UserEntity.class +}) +@JiraKey("HHH-15991") +public class DynamicInstantiationWithJoinAndGroupByAndParameterTest { + private static final String PARTNER_NUMBER = "1111111111"; + + @BeforeAll + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + final UserEntity user1 = new UserEntity( "John", "Tester" ); + entityManager.persist( user1 ); + entityManager.persist( new Action( PARTNER_NUMBER, "Test 1", user1 ) ); + entityManager.persist( new Action( PARTNER_NUMBER, "Test 2", user1 ) ); + } ); + } + + @AfterAll + public void tearDown(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + entityManager.createQuery( "delete from Action" ).executeUpdate(); + entityManager.createQuery( "delete from UserEntity" ).executeUpdate(); + } ); + } + + @Test + public void testIt(EntityManagerFactoryScope scope) { + scope.inTransaction( entityManager -> { + TypedQuery query = entityManager.createQuery( + "select new " + getClass().getName() + "$UserStatistic(u, count(a))" + + " from Action a inner join a.user u" + + " where a.partnerNumber = :partnerNumber" + + " group by u", + UserStatistic.class + ); + query.setParameter( "partnerNumber", PARTNER_NUMBER ); + UserStatistic result = query.getSingleResult(); + assertEquals( "John Tester", result.getName() ); + assertEquals( 2, result.getCount() ); + } ); + } + + @Entity(name = "UserEntity") + @Table(name = "UserEntity") + public static class UserEntity { + @Id + @GeneratedValue + private Long id; + + private String firstname; + + private String lastname; + + public UserEntity() { + } + + public UserEntity(String firstname, String lastname) { + this.firstname = firstname; + this.lastname = lastname; + } + + public Long getId() { + return id; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + } + + @Entity(name = "Action") + @Table(name = "Action") + public static class Action { + @Id + @GeneratedValue + private Long id; + + private String partnerNumber; + + private String title; + + @ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE }) + @JoinColumn(name = "fk_user_id") + private UserEntity user; + + public Action() { + } + + public Action(String partnerNumber, String title, UserEntity user) { + this.partnerNumber = partnerNumber; + this.title = title; + this.user = user; + } + + public Long getId() { + return id; + } + + public String getPartnerNumber() { + return partnerNumber; + } + + public void setPartnerNumber(String partnerNumber) { + this.partnerNumber = partnerNumber; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public UserEntity getUser() { + return user; + } + + public void setUser(UserEntity user) { + this.user = user; + } + } + + public static class UserStatistic { + private final String name; + + private final Long count; + + public UserStatistic(UserEntity user, Long count) { + this.name = user != null ? user.getFirstname() + " " + user.getLastname() : null; + this.count = count; + } + + public String getName() { + return name; + } + + public Integer getCount() { + return count.intValue(); + } + } +} From aa5e23311efbadac9253c389d2c32a223fe58158 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 30 Jan 2023 11:30:06 +0100 Subject: [PATCH 0023/1497] HHH-15998 Check dynamic instantiation arguments in group by clause --- .../sqm/sql/BaseSqmToSqlAstConverter.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) 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 c85e62bfe710..d2e60f80329c 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 @@ -2427,7 +2427,31 @@ private boolean selectClauseContains(SqmFrom from) { return true; } for ( SqmSelection selection : selections ) { - if ( selection.getSelectableNode() == from ) { + if ( selectableNodeContains( selection.getSelectableNode(), from ) ) { + return true; + } + } + return false; + } + + private boolean selectableNodeContains(SqmSelectableNode selectableNode, SqmFrom from) { + if ( selectableNode == from ) { + return true; + } + else if ( selectableNode instanceof SqmDynamicInstantiation ) { + for ( SqmDynamicInstantiationArgument argument : ( (SqmDynamicInstantiation) selectableNode ).getArguments() ) { + if ( selectableNodeContains( argument.getSelectableNode(), from ) ) { + return true; + } + } + } + return false; + } + + private boolean groupByClauseContains(SqmFrom from) { + final SqmQuerySpec sqmQuerySpec = (SqmQuerySpec) currentSqmQueryPart; + for ( SqmExpression expression : sqmQuerySpec.getGroupByClauseExpressions() ) { + if ( expression == from ) { return true; } } @@ -3888,6 +3912,11 @@ else if ( entityValuedModelPart instanceof AnonymousTupleEntityValuedModelPart ) // we need to expand to all columns, as we also expand this to all columns in the select clause expandToAllColumns = tableGroup.isFetched() || selectClauseContains( path ); } + else if ( currentClauseStack.getCurrent() == Clause.ORDER ) { + // We must ensure that the order by expression be expanded if the group by + // contained the same expression, and that was expanded as well + expandToAllColumns = groupByClauseContains( path ) && ( tableGroup.isFetched() || selectClauseContains( path ) ); + } else { expandToAllColumns = false; } From c4dc16a62424f5a43fa103e529c4fd3a3e2bfb18 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 1 Feb 2023 11:19:21 +0100 Subject: [PATCH 0024/1497] HHH-15969 Add test for issue --- ...eTableInheritanceEagerAssociationTest.java | 250 ++++++++++++++++++ ...leTableInheritanceLazyAssociationTest.java | 250 ++++++++++++++++++ 2 files changed, 500 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/inheritance/SingleTableInheritanceEagerAssociationTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/inheritance/SingleTableInheritanceLazyAssociationTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/inheritance/SingleTableInheritanceEagerAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/inheritance/SingleTableInheritanceEagerAssociationTest.java new file mode 100644 index 000000000000..b9129de396bf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/inheritance/SingleTableInheritanceEagerAssociationTest.java @@ -0,0 +1,250 @@ +package org.hibernate.orm.test.annotations.inheritance; + +import java.util.List; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Query; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + + +@Jpa( + annotatedClasses = { + SingleTableInheritanceEagerAssociationTest.Address.class, + SingleTableInheritanceEagerAssociationTest.AddressA.class, + SingleTableInheritanceEagerAssociationTest.AddressB.class, + SingleTableInheritanceEagerAssociationTest.User.class, + SingleTableInheritanceEagerAssociationTest.UserA.class, + SingleTableInheritanceEagerAssociationTest.UserB.class, + SingleTableInheritanceEagerAssociationTest.Message.class, + } +) +@TestForIssue(jiraKey = "HHH-15969") +public class SingleTableInheritanceEagerAssociationTest { + + @BeforeAll + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + // Create Object of Type A + AddressA addressA1 = new AddressA( "A1" ); + UserA userA1 = new UserA( "1" ); + addressA1.setUserA( userA1 ); + Message messageA1 = new Message( "MA1", addressA1 ); + entityManager.persist( messageA1 ); + + // Create Object of Type B + AddressB addressB1 = new AddressB( "B1" ); + UserB userB1 = new UserB( "2" ); + addressB1.setUserB( userB1 ); + Message messageB1 = new Message( "MB1", addressB1 ); + entityManager.persist( messageB1 ); + } + ); + } + + @Test + public void testQuery(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Query selectUFromUserU = entityManager.createQuery( "select u from Message u" ); + List resultList = selectUFromUserU.getResultList(); + + for ( Message message : resultList ) { + Address address = message.getAddress(); + address.getId(); + } + } + ); + } + + @Entity(name = "Message") + @Table(name = "T_MESSAGE") + public static class Message { + + @Id + private final String messageId; + + @ManyToOne( cascade = CascadeType.ALL) + @JoinColumn(name = "SENDER_ADDRESS_ID") + private final Address address; + + private int version; + + protected Message() { + this.messageId = null; + this.address = null; + } + + public Message(String messageId, Address address) { + this.messageId = messageId; + this.address = address; + } + + public String getId() { + return this.messageId; + } + + public Address getAddress() { + return address; + } + } + + @Entity(name = "Address") + @Table(name = "T_ADDRESS") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "USER_TYPE", discriminatorType = DiscriminatorType.STRING) + public static abstract class Address { + + @Id + private String addressId; + + private int version; + + protected Address() { + } + + public Address(String addressId) { + this.addressId = addressId; + } + + public String getId() { + return this.addressId; + } + + public abstract User getUser(); + } + + @Entity(name = "AddressA") + @DiscriminatorValue("ADDRESS_A") + public static class AddressA extends Address { + + protected AddressA() { + } + + public AddressA(String addressId) { + super( addressId ); + } + + @Override + public User getUser() { + return this.userA; + } + + @OneToOne(mappedBy = "addressA", cascade = CascadeType.ALL) + private UserA userA; + + public void setUserA(UserA userA) { + this.userA = userA; + userA.setAddressA( this ); + } + } + + @Entity(name = "AddressB") + @DiscriminatorValue("ADDRESS_B") + public static class AddressB extends Address { + + @OneToOne(mappedBy = "addressB", cascade = CascadeType.ALL) + private UserB userB; + + protected AddressB() { + } + + public AddressB(String addressId) { + super( addressId ); + } + + public void setUserB(UserB userB) { + this.userB = userB; + userB.setAddressB( this ); + } + + @Override + public User getUser() { + return this.userB; + } + } + + @Entity(name = "User") + @Table(name = "T_USER") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "USER_TYPE", discriminatorType = DiscriminatorType.STRING) + public static abstract class User { + + @Id + private final String userId; + + @Version + private int version; + + protected User() { + this.userId = null; + } + + public User(String id) { + this.userId = id; + } + + public String getId() { + return this.userId; + } + } + + @Entity(name = "UserA") + @DiscriminatorValue("USER_A") + public static class UserA extends User { + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "ADDRESS_ID") + private AddressA addressA; + + protected UserA() { + } + + public UserA(String userId) { + super( userId ); + } + + public void setAddressA(AddressA addressA) { + this.addressA = addressA; + } + } + + @Entity(name = "UserB") + @DiscriminatorValue("USER_B") + public static class UserB extends User { + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "ADDRESS_ID") + private AddressB addressB; + + protected UserB() { + } + + public UserB(String userId) { + super( userId ); + } + + public void setAddressB(AddressB addressB) { + this.addressB = addressB; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/inheritance/SingleTableInheritanceLazyAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/inheritance/SingleTableInheritanceLazyAssociationTest.java new file mode 100644 index 000000000000..40b1269329b1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/inheritance/SingleTableInheritanceLazyAssociationTest.java @@ -0,0 +1,250 @@ +package org.hibernate.orm.test.annotations.inheritance; + +import java.util.List; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Query; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + + +@Jpa( + annotatedClasses = { + SingleTableInheritanceLazyAssociationTest.Address.class, + SingleTableInheritanceLazyAssociationTest.AddressA.class, + SingleTableInheritanceLazyAssociationTest.AddressB.class, + SingleTableInheritanceLazyAssociationTest.User.class, + SingleTableInheritanceLazyAssociationTest.UserA.class, + SingleTableInheritanceLazyAssociationTest.UserB.class, + SingleTableInheritanceLazyAssociationTest.Message.class, + } +) +@TestForIssue(jiraKey = "HHH-15969") +public class SingleTableInheritanceLazyAssociationTest { + + @BeforeAll + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + // Create Object of Type A + AddressA addressA1 = new AddressA( "A1" ); + UserA userA1 = new UserA( "1" ); + addressA1.setUserA( userA1 ); + Message messageA1 = new Message( "MA1", addressA1 ); + entityManager.persist( messageA1 ); + + // Create Object of Type B + AddressB addressB1 = new AddressB( "B1" ); + UserB userB1 = new UserB( "2" ); + addressB1.setUserB( userB1 ); + Message messageB1 = new Message( "MB1", addressB1 ); + entityManager.persist( messageB1 ); + } + ); + } + + @Test + public void testQuery(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Query selectUFromUserU = entityManager.createQuery( "select u from Message u" ); + List resultList = selectUFromUserU.getResultList(); + + for ( Message message : resultList ) { + Address address = message.getAddress(); + address.getId(); + } + } + ); + } + + @Entity(name = "Message") + @Table(name = "T_MESSAGE") + public static class Message { + + @Id + private final String messageId; + + @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "SENDER_ADDRESS_ID") + private final Address address; + + private int version; + + protected Message() { + this.messageId = null; + this.address = null; + } + + public Message(String messageId, Address address) { + this.messageId = messageId; + this.address = address; + } + + public String getId() { + return this.messageId; + } + + public Address getAddress() { + return address; + } + } + + @Entity(name = "Address") + @Table(name = "T_ADDRESS") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "USER_TYPE", discriminatorType = DiscriminatorType.STRING) + public static abstract class Address { + + @Id + private String addressId; + + private int version; + + protected Address() { + } + + public Address(String addressId) { + this.addressId = addressId; + } + + public String getId() { + return this.addressId; + } + + public abstract User getUser(); + } + + @Entity(name = "AddressA") + @DiscriminatorValue("ADDRESS_A") + public static class AddressA extends Address { + + protected AddressA() { + } + + public AddressA(String addressId) { + super( addressId ); + } + + @Override + public User getUser() { + return this.userA; + } + + @OneToOne(mappedBy = "addressA", cascade = CascadeType.ALL) + private UserA userA; + + public void setUserA(UserA userA) { + this.userA = userA; + userA.setAddressA( this ); + } + } + + @Entity(name = "AddressB") + @DiscriminatorValue("ADDRESS_B") + public static class AddressB extends Address { + + @OneToOne(mappedBy = "addressB", cascade = CascadeType.ALL) + private UserB userB; + + protected AddressB() { + } + + public AddressB(String addressId) { + super( addressId ); + } + + public void setUserB(UserB userB) { + this.userB = userB; + userB.setAddressB( this ); + } + + @Override + public User getUser() { + return this.userB; + } + } + + @Entity(name = "User") + @Table(name = "T_USER") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "USER_TYPE", discriminatorType = DiscriminatorType.STRING) + public static abstract class User { + + @Id + private final String userId; + + @Version + private int version; + + protected User() { + this.userId = null; + } + + public User(String id) { + this.userId = id; + } + + public String getId() { + return this.userId; + } + } + + @Entity(name = "UserA") + @DiscriminatorValue("USER_A") + public static class UserA extends User { + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "ADDRESS_ID") + private AddressA addressA; + + protected UserA() { + } + + public UserA(String userId) { + super( userId ); + } + + public void setAddressA(AddressA addressA) { + this.addressA = addressA; + } + } + + @Entity(name = "UserB") + @DiscriminatorValue("USER_B") + public static class UserB extends User { + + @OneToOne(cascade = CascadeType.ALL) + @JoinColumn(name = "ADDRESS_ID") + private AddressB addressB; + + protected UserB() { + } + + public UserB(String userId) { + super( userId ); + } + + public void setAddressB(AddressB addressB) { + this.addressB = addressB; + } + } + +} From aad86110e6182457412e7a90545f779655490bcd Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 1 Feb 2023 16:13:32 +0100 Subject: [PATCH 0025/1497] HHH-15969 Inheritance: org.hibernate.PropertyAccessException Exception --- .../entity/AbstractEntityInitializer.java | 62 ++++++++++++++++++- ...luesSourceProcessingStateStandardImpl.java | 1 - 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java index da10d01e80b8..f02885a208ae 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java @@ -26,7 +26,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; -import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.spi.PreLoadEvent; import org.hibernate.event.spi.PreLoadEventListener; import org.hibernate.internal.util.StringHelper; @@ -51,6 +50,7 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.basic.BasicResultAssembler; import org.hibernate.sql.results.graph.entity.internal.EntityResultInitializer; import org.hibernate.sql.results.internal.NullValueAssembler; @@ -379,6 +379,10 @@ private boolean useEmbeddedIdentifierInstanceAsEntity(Object id) { @Override public void resolveInstance(RowProcessingState rowProcessingState) { if ( !missing && !isInitialized ) { + if ( shouldSkipResolveInstance( rowProcessingState ) ) { + missing = true; + return; + } // Special case map proxy to avoid stack overflows // We know that a map proxy will always be of "the right type" so just use that object final LoadingEntityEntry existingLoadingEntry = @@ -395,6 +399,62 @@ else if ( existingLoadingEntry != null && existingLoadingEntry.getEntityInitiali } } + private boolean shouldSkipResolveInstance(RowProcessingState rowProcessingState) { + final NavigablePath parent = navigablePath.getParent(); + if ( parent != null ) { + final Initializer parentInitializer = rowProcessingState.resolveInitializer( parent ); + if ( parentInitializer != null && parentInitializer.isEntityInitializer() ) { + if ( isReferencedModelPartAssignableToConcreteParent( parentInitializer ) ) { + return true; + } + } + } + return false; + } + + private boolean isReferencedModelPartAssignableToConcreteParent(Initializer parentInitializer) { + final EntityPersister parentConcreteDescriptor = parentInitializer.asEntityInitializer() + .getConcreteDescriptor(); + if ( parentConcreteDescriptor != null && parentConcreteDescriptor.getEntityMetamodel().isPolymorphic()) { + final ModelPart concreteModelPart = parentConcreteDescriptor.findByPath( navigablePath.getLocalName() ); + if ( concreteModelPart == null + || !referencedModelPart.getJavaType().getJavaTypeClass() + .isAssignableFrom( concreteModelPart.getJavaType().getJavaTypeClass() ) ) { + /* + Given: + + class Message{ + + @ManyToOne + Address address; + } + + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + class Address{ + } + + class AddressA extends Address { + @OneToOne(mappedBy = "addressA") + UserA userA; + } + + class AddressB extends Address{ + @OneToOne(mappedBy = "addressB") + UserB userB; + } + + when we try to initialize the messages of Message.address, + there will be one EntityJoinedFetchInitializer for UserA and one for UserB so + when resolving AddressA we have to skip resolving the EntityJoinedFetchInitializer of UserB + and for AddressB skip resolving the EntityJoinedFetchInitializer of UserA + + */ + return true; + } + } + return false; + } + private void resolveEntityInstance( RowProcessingState rowProcessingState, LoadingEntityEntry existingLoadingEntry, diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java index 61e3ac1e66f7..69596497d664 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java @@ -15,7 +15,6 @@ import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; -import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.service.spi.EventListenerGroup; import org.hibernate.event.spi.EventSource; From 7f9e48f66a6853aea127edd0e27fa46dbc00ecb4 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 1 Feb 2023 17:38:10 +0100 Subject: [PATCH 0026/1497] HHH-15970 Add test for issue --- .../MappedSuperclassAndGenericsTest.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/MappedSuperclassAndGenericsTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/MappedSuperclassAndGenericsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/MappedSuperclassAndGenericsTest.java new file mode 100644 index 000000000000..08778bb3496a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/MappedSuperclassAndGenericsTest.java @@ -0,0 +1,90 @@ +package org.hibernate.orm.test.annotations.generics; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; + +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; + +@DomainModel( + annotatedClasses = { + MappedSuperclassAndGenericsTest.RawEntity.class, + MappedSuperclassAndGenericsTest.LongEntity.class + } +) +@SessionFactory +@TestForIssue( jiraKey = "HHH-15970") +public class MappedSuperclassAndGenericsTest { + + public enum MyEnum { + A, B + } + + @Test + public void testIt(SessionFactoryScope scope){ + scope.inTransaction( + session -> { + + } + ); + } + + @MappedSuperclass + public static abstract class ParameterizedParent { + + @Enumerated(EnumType.STRING) + private MyEnum myEnum; + + public MyEnum getMyEnum() { + return myEnum; + } + + public void setMyEnum(MyEnum myEnum) { + this.myEnum = myEnum; + } + + } + + @Entity(name = "RawEntity") + public static class RawEntity extends ParameterizedParent { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + @Column(name = "id", nullable = false) + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + + @Entity(name = "LongEntity") + public static class LongEntity extends ParameterizedParent { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + @Column(name = "id", nullable = false) + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + +} From 1837474f7365f6ab1960240fbd70ffe50fc58393 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 1 Feb 2023 08:38:47 +0100 Subject: [PATCH 0027/1497] HHH-16075 Add test for issue --- .../id/IdClassAndAssociationsTest.java | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java new file mode 100644 index 000000000000..c3a4d42f1bce --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java @@ -0,0 +1,230 @@ +package org.hibernate.orm.test.annotations.id; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +@Jpa( + annotatedClasses = { + IdClassAndAssociationsTest.CourseEnrollment.class, + IdClassAndAssociationsTest.Unit.class, + IdClassAndAssociationsTest.User.class, + IdClassAndAssociationsTest.UserRole.class + } +) +@TestForIssue( jiraKey = "HHH-16075") +public class IdClassAndAssociationsTest { + + @BeforeEach + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + Unit unit = new Unit( "unit 1" ); + User trainee = new User( "Roy", unit ); + + UserRole userRole = new UserRole( trainee, "Trainee" ); + + trainee.addUserRole( userRole ); + + CourseEnrollment courseEnrollment = new CourseEnrollment( 1l, trainee ); + + entityManager.persist( unit ); + entityManager.persist( trainee ); + entityManager.persist( userRole ); + + entityManager.persist( courseEnrollment ); + } + ); + } + + @Test + public void testIt(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + final List enrollments = entityManager.createQuery( + "SELECT c FROM CourseEnrollment c WHERE c.courseId = ?1 AND c.trainee.firstName like (?2)", + CourseEnrollment.class + ) + .setParameter( 1, 1L ) + .setParameter( 2, "Roy" ) + .getResultList(); + + assertEquals( 1, enrollments.size() ); + + User trainer = enrollments.get( 0 ).getTrainee(); + assertNotNull( trainer ); + Set roles = trainer.getRoles(); + } + ); + + } + + @Entity(name = "CourseEnrollment") + @Table(name = "course_enrollment") + public static class CourseEnrollment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "course") + Long courseId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "trainee", referencedColumnName = "id") + User trainee; + + public CourseEnrollment() { + } + + public CourseEnrollment(Long courseId, User trainee) { + this.courseId = courseId; + this.trainee = trainee; + } + + public Long getId() { + return id; + } + + public Long getCourseId() { + return courseId; + } + + public User getTrainee() { + return trainee; + } + + } + + @Entity(name = "Unit") + @Table(name = "units") + public static class Unit { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + private String name; + + public Unit() { + } + + public Unit(String name) { + this.name = name; + } + } + + @Entity(name = "User") + @Table(name = "users") + public static class User { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(name = "first_name") + private String firstName; + + @ManyToOne + @JoinColumn(name = "unit") + private Unit unit; + + @JoinColumn(name = "user_id", referencedColumnName = "id") + @OneToMany(fetch = FetchType.EAGER, orphanRemoval = true, cascade = CascadeType.ALL) + private Set roles = new HashSet<>(); + + public User() { + } + + public User(String firstName, Unit unit) { + this.firstName = firstName; + this.unit = unit; + } + + public Long getId() { + return id; + } + + public Set getRoles() { + return roles; + } + + public void addUserRole(UserRole role) { + this.roles.add( role ); + } + } + + @Entity(name = "UserRole") + @Table(name = "users_roles") + @IdClass(UserRoleId.class) + public static class UserRole { + + @Id + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @Id + @Column(name = "role_name") + private String roleName; + + public UserRole() { + } + + public UserRole(User user, String roleName) { + this.user = user; + this.roleName = roleName; + } + } + + public static class UserRoleId implements Serializable { + + private User user; + private String roleName; + + public UserRoleId() { + } + + public UserRoleId(User user, String roleName) { + this.user = user; + this.roleName = roleName; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + } +} From 1aa4f177b049bfe4a1ccf4c4aede1e7b79d72c74 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 20 Jan 2023 15:13:42 +0100 Subject: [PATCH 0028/1497] HHH-15944 Add test for issue --- .../PolymorphicQueriesWithJoinTest.java | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/polymorphic/PolymorphicQueriesWithJoinTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/polymorphic/PolymorphicQueriesWithJoinTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/polymorphic/PolymorphicQueriesWithJoinTest.java new file mode 100644 index 000000000000..28e810ad238e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/polymorphic/PolymorphicQueriesWithJoinTest.java @@ -0,0 +1,190 @@ +package org.hibernate.orm.test.polymorphic; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.TypedQuery; + +@DomainModel( + annotatedClasses = { + PolymorphicQueriesWithJoinTest.Person.class, + PolymorphicQueriesWithJoinTest.Dog.class, + PolymorphicQueriesWithJoinTest.Cat.class + } +) +@SessionFactory +@TestForIssue(jiraKey = "HHH-15944") +public class PolymorphicQueriesWithJoinTest { + + public static final Long PERSON_ID = 1l; + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Person owner = new Person( PERSON_ID ); + + Cat cat = new Cat(); + Dog dog = new Dog(); + + cat.addToOwners( owner ); + dog.addToOwners( owner ); + + session.persist( owner ); + session.persist( cat ); + session.persist( dog ); + } + ); + } + + @Test + public void testSelect(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List animals = session + .createQuery( + "SELECT an FROM org.hibernate.orm.test.polymorphic.PolymorphicQueriesWithJoinTest$Animal an", + Animal.class + ) + .getResultList(); + + } + ); + } + + @Test + public void testSelectWithJoin(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List owners = session + .createQuery( + "SELECT p FROM org.hibernate.orm.test.polymorphic.PolymorphicQueriesWithJoinTest$Animal an JOIN an.owners p", + Person.class + ) + .getResultList(); + } + + ); + } + + @Test + public void testSelectWithFetchJoin(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List animals = session + .createQuery( + "SELECT an FROM org.hibernate.orm.test.polymorphic.PolymorphicQueriesWithJoinTest$Animal an JOIN FETCH an.owners p", + Animal.class + ) + .getResultList(); + } + + ); + } + + @Test + public void testSelectWithWhereClause(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Person owner = session.find( Person.class, PERSON_ID ); + TypedQuery query = session.createQuery( + "SELECT an FROM org.hibernate.orm.test.polymorphic.PolymorphicQueriesWithJoinTest$Animal an WHERE :owner = some elements(an.owners) ", + Animal.class + ); + query.setParameter( "owner", owner ); + List animals = query.getResultList(); + } + + ); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private long id; + + private String name; + + public Person() { + } + + public Person(long id) { + this.id = id; + } + + public long getId() { + return this.id; + } + } + + + public interface Animal { + Set getOwners(); + + void addToOwners(Person person); + } + + @Entity(name = "Cat") + public static class Cat implements Animal { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + private String name; + + @ManyToMany + private Set owners = new HashSet<>(); + + @Override + public Set getOwners() { + return this.owners; + } + + @Override + public void addToOwners(Person person) { + owners.add( person ); + } + + } + + + @Entity(name = "Dog") + public static class Dog implements Animal { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + private String name; + + @ManyToMany + private Set owners = new HashSet<>(); + + @Override + public Set getOwners() { + return this.owners; + } + + @Override + public void addToOwners(Person person) { + owners.add( person ); + } + } +} From 26ef29bb0d58a9c5fb71678d41231fad6b0d78e0 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 20 Jan 2023 21:23:51 +0100 Subject: [PATCH 0029/1497] HHH-15944 Joins on non-managed-type polymorphisms don't work --- .../query/hql/internal/QuerySplitter.java | 396 ++++++++++++++++-- .../query/sqm/SemanticQueryWalker.java | 75 ++++ .../query/sqm/tree/domain/SqmBagJoin.java | 7 + .../sqm/tree/domain/SqmCorrelatedBagJoin.java | 7 + .../tree/domain/SqmCorrelatedCrossJoin.java | 7 + .../tree/domain/SqmCorrelatedEntityJoin.java | 7 + .../tree/domain/SqmCorrelatedListJoin.java | 6 + .../sqm/tree/domain/SqmCorrelatedMapJoin.java | 6 + .../domain/SqmCorrelatedPluralPartJoin.java | 12 + .../sqm/tree/domain/SqmCorrelatedRoot.java | 4 +- .../tree/domain/SqmCorrelatedRootJoin.java | 5 +- .../sqm/tree/domain/SqmCorrelatedSetJoin.java | 6 + .../domain/SqmCorrelatedSingularJoin.java | 6 + .../query/sqm/tree/domain/SqmCorrelation.java | 1 + .../query/sqm/tree/domain/SqmListJoin.java | 7 + .../query/sqm/tree/domain/SqmMapJoin.java | 7 + .../query/sqm/tree/domain/SqmSetJoin.java | 7 + .../sqm/tree/domain/SqmSingularJoin.java | 6 + .../query/sqm/tree/from/SqmAttributeJoin.java | 21 +- 19 files changed, 533 insertions(+), 60 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java index 977c07521543..83d1f64eee38 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/hql/internal/QuerySplitter.java @@ -22,10 +22,31 @@ import org.hibernate.query.sqm.tree.SqmQuery; import org.hibernate.query.sqm.tree.cte.SqmCteTableColumn; import org.hibernate.query.sqm.tree.cte.SqmSearchClauseSpecification; +import org.hibernate.query.sqm.tree.domain.SqmBagJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedBagJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedCrossJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedEntityJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedListJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedMapJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedPluralPartJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRoot; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRootJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedSetJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedSingularJoin; import org.hibernate.query.sqm.tree.domain.SqmCteRoot; import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot; +import org.hibernate.query.sqm.tree.domain.SqmListJoin; +import org.hibernate.query.sqm.tree.domain.SqmMapJoin; +import org.hibernate.query.sqm.tree.domain.SqmSetJoin; +import org.hibernate.query.sqm.tree.domain.SqmSingularJoin; +import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; +import org.hibernate.query.sqm.tree.domain.SqmTreatedRoot; +import org.hibernate.query.sqm.tree.domain.SqmTreatedSimplePath; +import org.hibernate.query.sqm.tree.expression.SqmAny; +import org.hibernate.query.sqm.tree.expression.SqmEvery; import org.hibernate.query.sqm.tree.from.SqmCteJoin; import org.hibernate.query.sqm.tree.from.SqmDerivedJoin; +import org.hibernate.query.sqm.tree.from.SqmJoin; import org.hibernate.query.sqm.tree.select.SqmSelectQuery; import org.hibernate.spi.NavigablePath; import org.hibernate.query.hql.spi.SqmCreationOptions; @@ -54,7 +75,6 @@ import org.hibernate.query.sqm.tree.expression.SqmNamedParameter; import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter; import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation; -import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmCrossJoin; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -190,8 +210,8 @@ private static class UnmappedPolymorphismReplacer extends BaseSemanticQueryWa private final SqmCreationContext creationContext; private final Stack processingStateStack = new StandardStack<>( SqmCreationProcessingState.class ); - private Map sqmPathCopyMap = new HashMap<>(); - private Map sqmFromCopyMap = new HashMap<>(); + private final Map sqmPathCopyMap = new HashMap<>(); + private final Map sqmFromCopyMap = new HashMap<>(); private UnmappedPolymorphismReplacer( SqmRoot unmappedPolymorphicFromElement, @@ -598,16 +618,28 @@ public SqmCrossJoin visitCrossJoin(SqmCrossJoin join) { if ( sqmFrom != null ) { return (SqmCrossJoin) sqmFrom; } - final SqmRoot sqmRoot = (SqmRoot) sqmFromCopyMap.get( join.findRoot() ); final SqmCrossJoin copy = new SqmCrossJoin<>( join.getReferencedPathSource(), join.getExplicitAlias(), - sqmRoot + (SqmRoot) sqmFromCopyMap.get( join.findRoot() ) ); - getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); - sqmFromCopyMap.put( join, copy ); - sqmPathCopyMap.put( join.getNavigablePath(), copy ); - sqmRoot.addSqmJoin( copy ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmCorrelatedCrossJoin visitCorrelatedCrossJoin(SqmCorrelatedCrossJoin join) { + final SqmFrom sqmFrom = sqmFromCopyMap.get( join ); + if ( sqmFrom != null ) { + return (SqmCorrelatedCrossJoin) sqmFrom; + } + + final SqmCorrelatedCrossJoin copy = new SqmCorrelatedCrossJoin<>( + findSqmFromCopy( join.getCorrelationParent() ) + ); + + visitJoins( join, copy ); return copy; } @@ -617,18 +649,29 @@ public SqmCrossJoin visitCrossJoin(SqmCrossJoin join) { if ( sqmFrom != null ) { return (SqmPluralPartJoin) sqmFrom; } - final SqmFrom newLhs = (SqmFrom) sqmFromCopyMap.get( join.getLhs() ); final SqmPluralPartJoin copy = new SqmPluralPartJoin<>( - newLhs, + (SqmFrom) sqmFromCopyMap.get( join.getLhs() ), join.getReferencedPathSource(), join.getExplicitAlias(), join.getSqmJoinType(), join.nodeBuilder() ); - getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); - sqmFromCopyMap.put( join, copy ); - sqmPathCopyMap.put( join.getNavigablePath(), copy ); - newLhs.addSqmJoin( copy ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmCorrelatedPluralPartJoin visitCorrelatedPluralPartJoin(SqmCorrelatedPluralPartJoin join) { + final SqmFrom sqmFrom = sqmFromCopyMap.get( join ); + if ( sqmFrom != null ) { + return (SqmCorrelatedPluralPartJoin) sqmFrom; + } + final SqmCorrelatedPluralPartJoin copy = new SqmCorrelatedPluralPartJoin<>( + findSqmFromCopy( join.getCorrelationParent() ) + ); + + visitJoins( join, copy ); return copy; } @@ -645,24 +688,235 @@ public SqmEntityJoin visitQualifiedEntityJoin(SqmEntityJoin join) { join.getSqmJoinType(), sqmRoot ); - getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); - sqmFromCopyMap.put( join, copy ); - sqmPathCopyMap.put( join.getNavigablePath(), copy ); - sqmRoot.addSqmJoin( copy ); + + visitJoins( join, copy ); return copy; } @Override - public SqmAttributeJoin visitQualifiedAttributeJoin(SqmAttributeJoin join) { - SqmFrom sqmFrom = sqmFromCopyMap.get( join ); + public SqmCorrelatedRootJoin visitCorrelatedRootJoin(SqmCorrelatedRootJoin correlatedRootJoin) { + final SqmCorrelatedRootJoin sqmFrom = (SqmCorrelatedRootJoin) sqmFromCopyMap.get( correlatedRootJoin ); if ( sqmFrom != null ) { - return (SqmAttributeJoin) sqmFrom; + return sqmFrom; } - SqmAttributeJoin copy = join.makeCopy( getProcessingStateStack().getCurrent() ); - getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); - sqmFromCopyMap.put( join, copy ); - sqmPathCopyMap.put( join.getNavigablePath(), copy ); - ( (SqmFrom) copy.getParent() ).addSqmJoin( copy ); + SqmCorrelatedRootJoin copy = new SqmCorrelatedRootJoin<>( + correlatedRootJoin.getNavigablePath(), + correlatedRootJoin.getReferencedPathSource(), + correlatedRootJoin.nodeBuilder() + ); + correlatedRootJoin.visitReusablePaths( path -> path.accept( this ) ); + + sqmFromCopyMap.put( correlatedRootJoin, copy ); + sqmPathCopyMap.put( correlatedRootJoin.getNavigablePath(), copy ); + correlatedRootJoin.visitSqmJoins( + sqmJoin -> + sqmJoin.accept( this ) + ); + + return copy; + } + + @Override + public SqmCorrelatedRoot visitCorrelatedRoot(SqmCorrelatedRoot correlatedRoot) { + final SqmCorrelatedRoot sqmFrom = (SqmCorrelatedRoot) sqmFromCopyMap.get( correlatedRoot ); + if ( sqmFrom != null ) { + return sqmFrom; + } + SqmCorrelatedRoot copy = new SqmCorrelatedRoot<>( + findSqmFromCopy( correlatedRoot.getCorrelationParent() ) + ); + + correlatedRoot.visitReusablePaths( path -> path.accept( this ) ); + + sqmFromCopyMap.put( correlatedRoot, copy ); + sqmPathCopyMap.put( correlatedRoot.getNavigablePath(), copy ); + correlatedRoot.visitSqmJoins( + sqmJoin -> + sqmJoin.accept( this ) + ); + return copy; + } + + @Override + public SqmBagJoin visitBagJoin(SqmBagJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + if ( existing != null ) { + return (SqmBagJoin) existing; + } + + final SqmBagJoin copy = new SqmBagJoin<>( + findSqmFromCopy( join.getLhs() ), + join.getReferencedPathSource(), + join.getExplicitAlias(), + join.getSqmJoinType(), + join.isFetched(), + join.nodeBuilder() + ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmCorrelatedBagJoin visitCorrelatedBagJoin(SqmCorrelatedBagJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + if ( existing != null ) { + return (SqmCorrelatedBagJoin) existing; + } + + final SqmCorrelatedBagJoin copy = new SqmCorrelatedBagJoin<>( + findSqmFromCopy( join.getCorrelationParent() ) + ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmCorrelatedListJoin visitCorrelatedListJoin(SqmCorrelatedListJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + + if ( existing != null ) { + return (SqmCorrelatedListJoin) existing; + } + + final SqmCorrelatedListJoin copy = new SqmCorrelatedListJoin<>( + findSqmFromCopy( join.getCorrelationParent() ) + ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmCorrelatedMapJoin visitCorrelatedMapJoin(SqmCorrelatedMapJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + + if ( existing != null ) { + return (SqmCorrelatedMapJoin) existing; + } + + final SqmCorrelatedMapJoin copy = new SqmCorrelatedMapJoin<>( + findSqmFromCopy( join.getCorrelationParent() ) + ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmCorrelatedSetJoin visitCorrelatedSetJoin(SqmCorrelatedSetJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + + if ( existing != null ) { + return (SqmCorrelatedSetJoin) existing; + } + + final SqmCorrelatedSetJoin copy = new SqmCorrelatedSetJoin<>( + findSqmFromCopy( join.getCorrelationParent() ) + ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmCorrelatedSingularJoin visitCorrelatedSingularJoin(SqmCorrelatedSingularJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + + if ( existing != null ) { + return (SqmCorrelatedSingularJoin) existing; + } + + final SqmCorrelatedSingularJoin copy = new SqmCorrelatedSingularJoin<>( + findSqmFromCopy( join.getCorrelationParent() ) + ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmListJoin visitListJoin(SqmListJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + + if ( existing != null ) { + return (SqmListJoin) existing; + } + + final SqmListJoin copy = new SqmListJoin<>( + findSqmFromCopy( join.getLhs() ), + join.getReferencedPathSource(), + join.getExplicitAlias(), + join.getSqmJoinType(), + join.isFetched(), + join.nodeBuilder() + ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmMapJoin visitMapJoin(SqmMapJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + + if ( existing != null ) { + return (SqmMapJoin) existing; + } + + final SqmMapJoin copy = new SqmMapJoin<>( + findSqmFromCopy( join.getLhs() ), + join.getReferencedPathSource(), + join.getExplicitAlias(), + join.getSqmJoinType(), + join.isFetched(), + join.nodeBuilder() + ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmSetJoin visitSetJoin(SqmSetJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + + if ( existing != null ) { + return (SqmSetJoin) existing; + } + + final SqmSetJoin copy = new SqmSetJoin<>( + findSqmFromCopy( join.getLhs() ), + join.getReferencedPathSource(), + join.getExplicitAlias(), + join.getSqmJoinType(), + join.isFetched(), + join.nodeBuilder() + ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmSingularJoin visitSingularJoin(SqmSingularJoin join) { + final SqmFrom existing = sqmFromCopyMap.get( join ); + + if ( existing != null ) { + return (SqmSingularJoin) existing; + } + + final SqmSingularJoin copy = new SqmSingularJoin<>( + findSqmFromCopy( join.getLhs() ), + join.getReferencedPathSource(), + join.getExplicitAlias(), + join.getSqmJoinType(), + join.isFetched(), + join.nodeBuilder() + ); + + visitJoins( join, copy ); return copy; } @@ -680,10 +934,23 @@ public SqmDerivedJoin visitQualifiedDerivedJoin(SqmDerivedJoin join) { join.isLateral(), sqmRoot ); - getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); - sqmFromCopyMap.put( join, copy ); - sqmPathCopyMap.put( join.getNavigablePath(), copy ); - sqmRoot.addSqmJoin( copy ); + + visitJoins( join, copy ); + return copy; + } + + @Override + public SqmCorrelatedEntityJoin visitCorrelatedEntityJoin(SqmCorrelatedEntityJoin join) { + SqmFrom sqmFrom = sqmFromCopyMap.get( join ); + if ( sqmFrom != null ) { + return (SqmCorrelatedEntityJoin) sqmFrom; + } + + final SqmCorrelatedEntityJoin copy = new SqmCorrelatedEntityJoin( + findSqmFromCopy( join.getCorrelationParent() ) + ); + + visitJoins( join, copy ); return copy; } @@ -700,10 +967,8 @@ public SqmCteJoin visitQualifiedCteJoin(SqmCteJoin join) { join.getSqmJoinType(), sqmRoot ); - getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); - sqmFromCopyMap.put( join, copy ); - sqmPathCopyMap.put( join.getNavigablePath(), copy ); - sqmRoot.addSqmJoin( copy ); + + visitJoins( join, copy ); return copy; } @@ -711,7 +976,7 @@ public SqmCteJoin visitQualifiedCteJoin(SqmCteJoin join) { public SqmBasicValuedSimplePath visitBasicValuedPath(SqmBasicValuedSimplePath path) { final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry(); - final SqmPath lhs = findLhs( path, pathRegistry ); + final SqmPath lhs = findLhs( path ); final SqmBasicValuedSimplePath copy = new SqmBasicValuedSimplePath<>( lhs.getNavigablePath().append( path.getNavigablePath().getLocalName() ), @@ -728,7 +993,7 @@ public SqmBasicValuedSimplePath visitBasicValuedPath(SqmBasicValuedSimplePath @Override public SqmEmbeddedValuedSimplePath visitEmbeddableValuedPath(SqmEmbeddedValuedSimplePath path) { final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry(); - final SqmPath lhs = findLhs( path, pathRegistry ); + final SqmPath lhs = findLhs( path ); final SqmEmbeddedValuedSimplePath copy = new SqmEmbeddedValuedSimplePath<>( lhs.getNavigablePath().append( path.getNavigablePath().getLocalName() ), path.getReferencedPathSource(), @@ -743,7 +1008,7 @@ public SqmEmbeddedValuedSimplePath visitEmbeddableValuedPath(SqmEmbeddedValue @Override public SqmEntityValuedSimplePath visitEntityValuedPath(SqmEntityValuedSimplePath path) { final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry(); - final SqmPath lhs = findLhs( path, pathRegistry ); + final SqmPath lhs = findLhs( path ); final SqmEntityValuedSimplePath copy = new SqmEntityValuedSimplePath<>( lhs.getNavigablePath().append( path.getNavigablePath().getLocalName() ), path.getReferencedPathSource(), @@ -758,7 +1023,7 @@ public SqmEntityValuedSimplePath visitEntityValuedPath(SqmEntityValuedSimpleP @Override public SqmPluralValuedSimplePath visitPluralValuedPath(SqmPluralValuedSimplePath path) { final SqmPathRegistry pathRegistry = getProcessingStateStack().getCurrent().getPathRegistry(); - SqmPath lhs = findLhs( path, pathRegistry ); + SqmPath lhs = findLhs( path ); final SqmPluralValuedSimplePath copy = new SqmPluralValuedSimplePath<>( path.getNavigablePath(), @@ -771,11 +1036,11 @@ public SqmPluralValuedSimplePath visitPluralValuedPath(SqmPluralValuedSimpleP return copy; } - private SqmPath findLhs(SqmPath path, SqmPathRegistry pathRegistry) { + private SqmPath findLhs(SqmPath path) { final SqmPath lhs = path.getLhs(); - final SqmPath sqmFrom = sqmPathCopyMap.get( lhs.getNavigablePath() ); - if ( sqmFrom != null ) { - return pathRegistry.findFromByPath( sqmFrom.getNavigablePath() ); + final SqmPath lhsSqmPathCopy = sqmPathCopyMap.get( lhs.getNavigablePath() ); + if ( lhsSqmPathCopy != null ) { + return lhsSqmPathCopy; } else { return (SqmPath) lhs.accept( this ); @@ -1022,6 +1287,28 @@ public SqmSubQuery visitSubQueryExpression(SqmSubQuery expression) { ); } + @Override + public Object visitAny(SqmAny sqmAny) { + return new SqmAny<>( + (SqmSubQuery) sqmAny.getSubquery().accept( this ), + this.creationContext.getNodeBuilder() + ); + } + + @Override + public Object visitTreatedPath(SqmTreatedPath sqmTreatedPath) { + throw new UnsupportedOperationException("Polymorphic queries: treat operator is not supported"); + } + + @Override + public Object visitEvery(SqmEvery sqmEvery) { + sqmEvery.getSubquery().accept( this ); + return new SqmEvery<>( + (SqmSubQuery) sqmEvery.getSubquery().accept( this ), + this.creationContext.getNodeBuilder() + ); + } + @Override public Stack getProcessingStateStack() { return processingStateStack; @@ -1037,6 +1324,29 @@ public SqmCreationOptions getCreationOptions() { return () -> false; } + public > X findSqmFromCopy(SqmFrom sqmFrom) { + SqmFrom copy = sqmFromCopyMap.get( sqmFrom ); + if ( copy != null ) { + return (X) copy; + } + else { + return (X) sqmFrom.accept( this ); + } + } + + private void visitJoins(SqmFrom join, SqmJoin copy) { + getProcessingStateStack().getCurrent().getPathRegistry().register( copy ); + sqmFromCopyMap.put( join, copy ); + sqmPathCopyMap.put( join.getNavigablePath(), copy ); + join.visitSqmJoins( + sqmJoin -> + sqmJoin.accept( this ) + ); + SqmFrom lhs = (SqmFrom) copy.getLhs(); + if ( lhs != null ) { + lhs.addSqmJoin( copy ); + } + } } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java index e12a5ee2deae..cc1e79cc2fa2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SemanticQueryWalker.java @@ -15,7 +15,18 @@ import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath; import org.hibernate.query.sqm.tree.domain.SqmAnyValuedSimplePath; +import org.hibernate.query.sqm.tree.domain.SqmBagJoin; import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedBagJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedCrossJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedEntityJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedListJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedMapJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedPluralPartJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRoot; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedRootJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedSetJoin; +import org.hibernate.query.sqm.tree.domain.SqmCorrelatedSingularJoin; import org.hibernate.query.sqm.tree.domain.SqmCorrelation; import org.hibernate.query.sqm.tree.domain.SqmCteRoot; import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot; @@ -23,11 +34,15 @@ import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmFkExpression; import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath; +import org.hibernate.query.sqm.tree.domain.SqmListJoin; import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference; import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction; import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction; +import org.hibernate.query.sqm.tree.domain.SqmMapJoin; import org.hibernate.query.sqm.tree.domain.SqmPluralPartJoin; import org.hibernate.query.sqm.tree.domain.SqmPluralValuedSimplePath; +import org.hibernate.query.sqm.tree.domain.SqmSetJoin; +import org.hibernate.query.sqm.tree.domain.SqmSingularJoin; import org.hibernate.query.sqm.tree.domain.SqmTreatedPath; import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter; import org.hibernate.query.sqm.tree.expression.SqmAny; @@ -147,6 +162,58 @@ public interface SemanticQueryWalker { T visitQualifiedAttributeJoin(SqmAttributeJoin joinedFromElement); + default T visitCorrelatedCrossJoin(SqmCorrelatedCrossJoin join) { + return visitCrossJoin( join ); + } + + default T visitCorrelatedEntityJoin(SqmCorrelatedEntityJoin join) { + return visitQualifiedEntityJoin( join ); + } + + default T visitCorrelatedPluralPartJoin(SqmCorrelatedPluralPartJoin join) { + return visitPluralPartJoin( join ); + } + + default T visitBagJoin(SqmBagJoin join){ + return visitQualifiedAttributeJoin( join ); + } + + default T visitCorrelatedBagJoin(SqmCorrelatedBagJoin join) { + return visitQualifiedAttributeJoin( join ); + } + + default T visitCorrelatedListJoin(SqmCorrelatedListJoin join) { + return visitQualifiedAttributeJoin( join ); + } + + default T visitCorrelatedMapJoin(SqmCorrelatedMapJoin join) { + return visitQualifiedAttributeJoin( join ); + } + + default T visitCorrelatedSetJoin(SqmCorrelatedSetJoin join) { + return visitQualifiedAttributeJoin( join ); + } + + default T visitCorrelatedSingularJoin(SqmCorrelatedSingularJoin join) { + return visitQualifiedAttributeJoin( join ); + } + + default T visitListJoin(SqmListJoin join) { + return visitQualifiedAttributeJoin( join ); + } + + default T visitMapJoin(SqmMapJoin join) { + return visitQualifiedAttributeJoin( join ); + } + + default T visitSetJoin(SqmSetJoin join) { + return visitQualifiedAttributeJoin( join ); + } + + default T visitSingularJoin(SqmSingularJoin join) { + return visitQualifiedAttributeJoin( join ); + } + T visitQualifiedDerivedJoin(SqmDerivedJoin joinedFromElement); T visitQualifiedCteJoin(SqmCteJoin joinedFromElement); @@ -177,6 +244,13 @@ public interface SemanticQueryWalker { T visitCorrelation(SqmCorrelation correlation); + default T visitCorrelatedRootJoin(SqmCorrelatedRootJoin correlatedRootJoin){ + return visitCorrelation( correlatedRootJoin ); + } + + default T visitCorrelatedRoot(SqmCorrelatedRoot correlatedRoot){ + return visitCorrelation( correlatedRoot ); + } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Query spec @@ -328,4 +402,5 @@ public interface SemanticQueryWalker { T visitMapEntryFunction(SqmMapEntryReference function); T visitFullyQualifiedClass(Class namedClass); + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java index 95fc623aacbd..8e15b1586a5b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmBagJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.BagPersistentAttribute; import org.hibernate.metamodel.model.domain.EntityDomainType; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.spi.NavigablePath; import org.hibernate.query.criteria.JpaCollectionJoin; import org.hibernate.query.criteria.JpaExpression; @@ -81,6 +82,11 @@ public BagPersistentAttribute getModel() { return getReferencedPathSource(); } + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitBagJoin( this ); + } + @Override public BagPersistentAttribute getAttribute() { //noinspection unchecked @@ -149,4 +155,5 @@ public SqmAttributeJoin makeCopy(SqmCreationProcessingState creationProces nodeBuilder() ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java index 7daca4370b87..1be4b7cee837 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedBagJoin.java @@ -10,6 +10,7 @@ import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -74,6 +75,11 @@ public SqmCorrelatedBagJoin copy(SqmCopyContext context) { return path; } + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitCorrelatedBagJoin( this ); + } + @Override public SqmBagJoin getCorrelationParent() { return correlationParent; @@ -108,4 +114,5 @@ public SqmCorrelatedBagJoin makeCopy(SqmCreationProcessingState creationPr pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java index 72dc71a8ad6f..4861bf96cadb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedCrossJoin.java @@ -9,6 +9,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.from.SqmCrossJoin; import org.hibernate.query.sqm.tree.from.SqmRoot; @@ -94,4 +95,10 @@ public SqmCorrelatedCrossJoin makeCopy(SqmCreationProcessingState creationPro pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) ); } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitCorrelatedCrossJoin( this ); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java index 589ba9821dcb..0ad2cf1c2ed4 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedEntityJoin.java @@ -9,6 +9,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmEntityJoin; @@ -67,6 +68,11 @@ public SqmCorrelatedEntityJoin copy(SqmCopyContext context) { return path; } + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitCorrelatedEntityJoin(this); + } + @Override public SqmEntityJoin getCorrelationParent() { return correlationParent; @@ -99,4 +105,5 @@ public SqmCorrelatedEntityJoin makeCopy(SqmCreationProcessingState creationPr pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java index 4bae7141b646..99b815a2bf86 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedListJoin.java @@ -10,6 +10,7 @@ import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -108,4 +109,9 @@ public SqmCorrelatedListJoin makeCopy(SqmCreationProcessingState creationP pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) ); } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitCorrelatedListJoin( this ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java index 3c0111040913..4f0f12471353 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedMapJoin.java @@ -10,6 +10,7 @@ import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -108,4 +109,9 @@ public SqmCorrelatedMapJoin makeCopy(SqmCreationProcessingState creatio pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) ); } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitCorrelatedMapJoin( this ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java index ea01981c1e17..21c5e67d861b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedPluralPartJoin.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.sqm.tree.domain; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -46,6 +47,16 @@ public SqmCorrelatedPluralPartJoin copy(SqmCopyContext context) { return path; } + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitCorrelatedPluralPartJoin( this ); + } + + @Override + public SqmFrom getLhs() { + return (SqmFrom) super.getLhs(); + } + @Override public SqmPluralPartJoin getCorrelationParent() { return correlationParent; @@ -65,4 +76,5 @@ public boolean isCorrelated() { public SqmRoot getCorrelatedRoot() { return correlatedRootJoin; } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java index 87f907c86567..46b6a2e6cccc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRoot.java @@ -14,7 +14,7 @@ /** * @author Steve Ebersole */ -public class SqmCorrelatedRoot extends SqmRoot implements SqmPathWrapper, SqmCorrelation { +public class SqmCorrelatedRoot extends SqmRoot implements SqmPathWrapper, SqmCorrelation { private final SqmRoot correlationParent; @@ -80,6 +80,6 @@ public SqmRoot getCorrelatedRoot() { @Override public X accept(SemanticQueryWalker walker) { - return walker.visitCorrelation( this ); + return walker.visitCorrelatedRoot( this ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java index 156ccf15abae..cd03c5bc6192 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedRootJoin.java @@ -20,7 +20,7 @@ */ public class SqmCorrelatedRootJoin extends SqmRoot implements SqmCorrelation { - SqmCorrelatedRootJoin( + public SqmCorrelatedRootJoin( NavigablePath navigablePath, SqmPathSource referencedNavigable, NodeBuilder nodeBuilder) { @@ -89,6 +89,7 @@ public SqmRoot getCorrelatedRoot() { @Override public X accept(SemanticQueryWalker walker) { - return walker.visitCorrelation( this ); + return walker.visitCorrelatedRootJoin( this ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java index e35ad86afabd..d9381bcfd8ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSetJoin.java @@ -10,6 +10,7 @@ import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -108,4 +109,9 @@ public SqmCorrelatedSetJoin makeCopy(SqmCreationProcessingState creationPr pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) ); } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitCorrelatedSetJoin( this ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java index 9b14bc7e1410..85283485a65e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelatedSingularJoin.java @@ -10,6 +10,7 @@ import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.hql.spi.SqmPathRegistry; import org.hibernate.query.sqm.NodeBuilder; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.from.SqmFrom; @@ -108,4 +109,9 @@ public SqmCorrelatedSingularJoin makeCopy(SqmCreationProcessingState creat pathRegistry.findFromByPath( correlationParent.getNavigablePath() ) ); } + + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitCorrelatedSingularJoin( this ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelation.java index ee24b8c7133d..cc498164130e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmCorrelation.java @@ -17,4 +17,5 @@ */ public interface SqmCorrelation extends SqmFrom, SqmPathWrapper { SqmRoot getCorrelatedRoot(); + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java index abf372b804b8..8bceb53f875a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmListJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.ListPersistentAttribute; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.spi.NavigablePath; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaListJoin; @@ -84,6 +85,11 @@ public ListPersistentAttribute getModel() { return getReferencedPathSource(); } + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitListJoin( this ); + } + @Override public ListPersistentAttribute getAttribute() { //noinspection unchecked @@ -156,4 +162,5 @@ public SqmAttributeJoin makeCopy(SqmCreationProcessingState creationProces nodeBuilder() ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java index 5ed057033e30..22f1ed6acc31 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmMapJoin.java @@ -13,6 +13,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.MapPersistentAttribute; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.spi.NavigablePath; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaMapJoin; @@ -83,6 +84,11 @@ public MapPersistentAttribute getModel() { return (MapPersistentAttribute) super.getModel(); } + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitMapJoin( this ); + } + @Override public MapPersistentAttribute getAttribute() { //noinspection unchecked @@ -170,4 +176,5 @@ public SqmMapJoin makeCopy(SqmCreationProcessingState creationProcessin nodeBuilder() ); } + } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java index 255080c26e7a..14b795e69f6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSetJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.SetPersistentAttribute; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.spi.NavigablePath; import org.hibernate.query.criteria.JpaExpression; import org.hibernate.query.criteria.JpaPredicate; @@ -83,6 +84,11 @@ public SetPersistentAttribute getModel() { return getReferencedPathSource(); } + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitSetJoin( this ); + } + @Override public SetPersistentAttribute getAttribute() { return getReferencedPathSource(); @@ -142,6 +148,7 @@ public SqmAttributeJoin fetch(String attributeName) { return fetch( attributeName, JoinType.INNER); } + @Override public SqmAttributeJoin makeCopy(SqmCreationProcessingState creationProcessingState) { return new SqmSetJoin<>( diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java index 5c27e551283b..e26828fb9d0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/SqmSingularJoin.java @@ -10,6 +10,7 @@ import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; +import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.spi.NavigablePath; import org.hibernate.query.hql.spi.SqmCreationProcessingState; import org.hibernate.query.sqm.NodeBuilder; @@ -43,6 +44,11 @@ public SqmSingularJoin( super( lhs, joinedNavigable, alias, joinType, fetched, nodeBuilder ); } + @Override + public X accept(SemanticQueryWalker walker) { + return walker.visitSingularJoin(this); + } + protected SqmSingularJoin( SqmFrom lhs, NavigablePath navigablePath, diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmAttributeJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmAttributeJoin.java index 720d855b3eba..ad2e5a2884ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmAttributeJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/from/SqmAttributeJoin.java @@ -6,13 +6,12 @@ */ package org.hibernate.query.sqm.tree.from; -import org.hibernate.HibernateException; +import org.hibernate.Remove; import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.query.criteria.JpaFetch; import org.hibernate.query.criteria.JpaJoin; -import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.hql.spi.SqmCreationProcessingState; -import org.hibernate.query.sqm.tree.domain.SqmPath; +import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.tree.predicate.SqmPredicate; import org.hibernate.type.descriptor.java.JavaType; @@ -44,15 +43,11 @@ public interface SqmAttributeJoin extends SqmQualifiedJoin, JpaFetch SqmAttributeJoin treatAs(EntityDomainType treatTarget); - SqmAttributeJoin makeCopy(SqmCreationProcessingState creationProcessingState); - - class NotJoinableException extends HibernateException { - public NotJoinableException(String message) { - super( message ); - } + /* + @deprecated not used anymore + */ + @Deprecated + @Remove + SqmAttributeJoin makeCopy( SqmCreationProcessingState creationProcessingState ); - public NotJoinableException(String message, Throwable cause) { - super( message, cause ); - } - } } From 03406be47ca4de7a326e0f997f5e6a8264800cb8 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 30 Jan 2023 12:47:13 +0100 Subject: [PATCH 0030/1497] HHH-16120 Add test for issue --- .../orm/test/collection/list/Child.java | 46 ++++++++++++++ .../IterateOverListInTheSetMethodTest.java | 40 ++++++++++++ .../orm/test/collection/list/Parent.java | 62 +++++++++++++++++++ .../list/ParentChildMapping.hbm.xml | 19 ++++++ 4 files changed, 167 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/Child.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/IterateOverListInTheSetMethodTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/Parent.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/Child.java b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/Child.java new file mode 100644 index 000000000000..e5e0e6654871 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/Child.java @@ -0,0 +1,46 @@ +package org.hibernate.orm.test.collection.list; + +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; + +public class Child { + private Integer id; + private String name; + + + private Parent parent; + + public Child() { + } + + public Child(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @ManyToOne + public Parent getParent() { + return parent; + } + + public void setParent(Parent parent) { + this.parent = parent; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/IterateOverListInTheSetMethodTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/IterateOverListInTheSetMethodTest.java new file mode 100644 index 000000000000..3e2c3998a382 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/IterateOverListInTheSetMethodTest.java @@ -0,0 +1,40 @@ +package org.hibernate.orm.test.collection.list; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +@DomainModel( + xmlMappings = "org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml" +) +@SessionFactory +public class IterateOverListInTheSetMethodTest { + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Child child = new Child( 1, "Luigi" ); + Child child2 = new Child( 2, "Franco" ); + Parent parent = new Parent( 2, "Fabio" ); + parent.addChild( child ); + parent.addChild( child2 ); + + session.persist( parent ); + session.persist( child ); + session.persist( child2 ); + } + ); + } + + @Test + public void testHqlQuery(SessionFactoryScope scope) { + scope.inSession( + session -> { + session.createQuery( "select p from Parent p" ).list(); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/Parent.java b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/Parent.java new file mode 100644 index 000000000000..f35007589cee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/Parent.java @@ -0,0 +1,62 @@ +package org.hibernate.orm.test.collection.list; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; + +public class Parent { + + private Integer id; + private String name; + + + private List children = new ArrayList<>(); + + public Parent() { + } + + public Parent(Integer id, String name) { + this.id = id; + this.name = name; + } + + @Id + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + for ( Iterator i = children.iterator(); i.hasNext(); ) { + if ( i.next() == null ) { + i.remove(); + } + } + } + + public void addChild(Child child) { + this.children.add( child ); + child.setParent( this ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml new file mode 100644 index 000000000000..77bcc3ad02af --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file From 010b8df7cd843394f3c16b663bf37db42903073f Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 30 Jan 2023 12:47:28 +0100 Subject: [PATCH 0031/1497] HHH-16120 Error advancing (next) ResultSet position --- .../collection/spi/AbstractPersistentCollection.java | 6 +++++- .../engine/internal/StatefulPersistenceContext.java | 5 +++++ .../java/org/hibernate/engine/spi/PersistenceContext.java | 5 +++++ .../JdbcValuesSourceProcessingStateStandardImpl.java | 1 - .../java/org/hibernate/sql/results/spi/LoadContexts.java | 5 ++++- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java index 5f6cc320238a..ce88e92539b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java @@ -286,7 +286,11 @@ else if ( !session.isConnected() ) { Whenever the collection lazy loading is triggered during the loading process, closing the connection will cause an error when RowProcessingStateStandardImpl#next() will be called. */ - if ( !session.isTransactionInProgress() && session.getPersistenceContext().isLoadFinished() ) { + final PersistenceContext persistenceContext = session.getPersistenceContext(); + if ( !session.isTransactionInProgress() + && ( !persistenceContext.hasLoadContext() + || ( persistenceContext.hasLoadContext() + && persistenceContext.getLoadContexts().isLoadingFinished() ) ) ) { session.getJdbcCoordinator().afterTransaction(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index c27a92cc9d33..f893f57bb9fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -201,6 +201,11 @@ public LoadContexts getLoadContexts() { return loadContexts; } + @Override + public boolean hasLoadContext() { + return loadContexts != null; + } + // @Override // public void addUnownedCollection(CollectionKey key, PersistentCollection collection) { // if ( unownedCollections == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java index a4328aa193ff..d3f96fedf795 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/PersistenceContext.java @@ -58,6 +58,11 @@ public interface PersistenceContext { */ LoadContexts getLoadContexts(); + default boolean hasLoadContext() { + getLoadContexts(); + return true; + } + // /** // * Add a collection which has no owner loaded // * diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java index 69596497d664..cdd16b228565 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java @@ -39,7 +39,6 @@ * @author Steve Ebersole */ public class JdbcValuesSourceProcessingStateStandardImpl implements JdbcValuesSourceProcessingState { - private static final Logger log = Logger.getLogger( JdbcValuesSourceProcessingStateStandardImpl.class ); private final ExecutionContext executionContext; private final JdbcValuesSourceProcessingOptions processingOptions; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/LoadContexts.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/LoadContexts.java index 23c9fe2b5455..11c6eb13995d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/LoadContexts.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/LoadContexts.java @@ -10,7 +10,6 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.StandardStack; @@ -48,6 +47,10 @@ public void deregister(JdbcValuesSourceProcessingState state) { } } + public boolean isLoadingFinished() { + return jdbcValuesSourceProcessingStateStack.getRoot() == null; + } + public LoadingEntityEntry findLoadingEntityEntry(final EntityKey entityKey) { return jdbcValuesSourceProcessingStateStack.findCurrentFirstWithParameter( entityKey, JdbcValuesSourceProcessingState::findLoadingEntityLocally ); } From efe5ee830d39ff51ea89c80398d3f88f00b4947a Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 30 Jan 2023 18:52:06 +0100 Subject: [PATCH 0032/1497] HHH-16117 Add test for issue --- ...ePersistAndQueryInSameTransactionTest.java | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/EmbeddablePersistAndQueryInSameTransactionTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/EmbeddablePersistAndQueryInSameTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/EmbeddablePersistAndQueryInSameTransactionTest.java new file mode 100644 index 000000000000..baa90452d330 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/EmbeddablePersistAndQueryInSameTransactionTest.java @@ -0,0 +1,190 @@ +package org.hibernate.orm.test.jpa.orphan.onetomany; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@Jpa( + annotatedClasses = { + EmbeddablePersistAndQueryInSameTransactionTest.Child.class, + EmbeddablePersistAndQueryInSameTransactionTest.Parent.class, + EmbeddablePersistAndQueryInSameTransactionTest.Dog.class + } +) +@TestForIssue(jiraKey = "HHH-16117") +class EmbeddablePersistAndQueryInSameTransactionTest { + + @Test + public void testIt(EntityManagerFactoryScope scope) { + + scope.inTransaction( + entityManager -> { + Child child = new Child( "Fab" ); + + Parent parent = new Parent( "1", "Paul", 40 ); + parent.addChild( child ); + + Dog dog = new Dog( "Pluto" ); + parent.addDog( dog ); + + entityManager.persist( parent ); + + Parent found = entityManager.createQuery( + "select e from Parent e where e.id = :id", Parent.class ) + .setParameter( "id", parent.getId() ) + .getSingleResult(); + + assertThat( found.getId() ).isEqualTo( parent.getId() ); + } ); + } + + @Entity(name = "Child") + public static class Child { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Basic + private String name; + + public Child() { + } + + public Child(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + } + + @Entity(name = "Dog") + public static class Dog { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Basic + private String name; + + public Dog() { + } + + public Dog(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } + + @Embeddable + public static class NestedEmbeddable { + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL) + private List dogs = new ArrayList<>(); + + public List getDogs() { + return dogs; + } + + public void addDog(Dog dog) { + dogs.add( dog ); + } + } + + @Embeddable + public static class EmbeddedData { + + private Integer age; + + @OneToMany(orphanRemoval = true, cascade = CascadeType.ALL) + private List children = new ArrayList<>(); + + @Embedded + private NestedEmbeddable nestedEmbeddable = new NestedEmbeddable(); + + public List getChildren() { + return Collections.unmodifiableList( children ); + } + + public void addChild(Child child) { + this.children.add( child ); + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public void addDog(Dog dog) { + nestedEmbeddable.addDog( dog ); + } + } + + @Entity(name = "Parent") + public static class Parent { + @Id + private String id; + + private String name; + + @Embedded + private final EmbeddedData data = new EmbeddedData(); + + public Parent() { + } + + public Parent(String id, String name, Integer age) { + this.id = id; + this.name = name; + data.setAge( age ); + } + + public String getId() { + return id; + } + + public void addChild(Child child) { + data.addChild( child ); + } + + public void addDog(Dog dog) { + data.addDog( dog ); + } + } + +} From c30084010ccaea88e3a4663bccc910d207c97150 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 31 Jan 2023 11:19:02 +0100 Subject: [PATCH 0033/1497] HHH-16117 Querying entity with collection in Embeddable causes 'A collection with cascade=all-delete-orphan was no longer referenced by the owning entity instance' --- .../internal/AbstractEntityInsertAction.java | 76 ++++++++++++++++--- .../action/internal/EntityInsertAction.java | 2 +- .../metamodel/mapping/AttributeMapping.java | 27 +++++++ .../mapping/PluralAttributeMapping.java | 10 +++ .../internal/EmbeddedAttributeMapping.java | 10 +++ 5 files changed, 112 insertions(+), 13 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java index bb22b8379dc9..2a5dbcaac0f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java @@ -18,10 +18,13 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.EventSource; +import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.AttributeMappingsList; import org.hibernate.persister.entity.EntityPersister; import static org.hibernate.engine.internal.Versioning.getVersion; @@ -148,20 +151,69 @@ public final void makeEntityManaged() { protected void addCollectionsByKeyToPersistenceContext(PersistenceContext persistenceContext, Object[] objects) { for ( int i = 0; i < objects.length; i++ ) { - if ( objects[i] instanceof PersistentCollection ) { - final PersistentCollection persistentCollection = (PersistentCollection) objects[i]; - final CollectionPersister collectionPersister = ( (PluralAttributeMapping) getPersister().getAttributeMapping( i ) ).getCollectionDescriptor(); - final CollectionKey collectionKey = new CollectionKey( - collectionPersister, - ( (AbstractEntityPersister) getPersister() ).getCollectionKey( - collectionPersister, - getInstance(), - persistenceContext.getEntry( getInstance() ), - getSession() - ) + final AttributeMapping attributeMapping = getPersister().getAttributeMapping( i ); + if ( attributeMapping.isEmbeddedAttributeMapping() ) { + visitEmbeddedAttributeMapping( + attributeMapping.asEmbeddedAttributeMapping(), + objects[i], + persistenceContext ); - persistenceContext.addCollectionByKey( collectionKey, persistentCollection ); } + else if ( attributeMapping.isPluralAttributeMapping() ) { + addCollectionKey( + attributeMapping.asPluralAttributeMapping(), + objects[i], + persistenceContext + ); + } + } + } + + private void visitEmbeddedAttributeMapping( + EmbeddedAttributeMapping attributeMapping, + Object object, + PersistenceContext persistenceContext) { + if ( object != null ) { + final AttributeMappingsList attributeMappings = attributeMapping.getEmbeddableTypeDescriptor().getAttributeMappings(); + for ( int i = 0; i < attributeMappings.size(); i++ ) { + final AttributeMapping attribute = attributeMappings.get( i ); + if ( attribute.isPluralAttributeMapping() ) { + addCollectionKey( + attribute.asPluralAttributeMapping(), + attribute.getPropertyAccess().getGetter().get( object ), + persistenceContext + ); + } + else if ( attribute.isEmbeddedAttributeMapping() ) { + visitEmbeddedAttributeMapping( + attribute.asEmbeddedAttributeMapping(), + attribute.getPropertyAccess().getGetter().get( object ), + persistenceContext + ); + } + } + } + } + + private void addCollectionKey( + PluralAttributeMapping pluralAttributeMapping, + Object o, + PersistenceContext persistenceContext) { + if ( o instanceof PersistentCollection ) { + final CollectionPersister collectionPersister = pluralAttributeMapping.getCollectionDescriptor(); + final CollectionKey collectionKey = new CollectionKey( + collectionPersister, + ( (AbstractEntityPersister) getPersister() ).getCollectionKey( + collectionPersister, + getInstance(), + persistenceContext.getEntry( getInstance() ), + getSession() + ) + ); + persistenceContext.addCollectionByKey( + collectionKey, + (PersistentCollection) o + ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java index 8cae0da98268..9e34db5dd756 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/EntityInsertAction.java @@ -100,7 +100,7 @@ public void execute() throws HibernateException { final EntityPersister persister = getPersister(); final Object instance = getInstance(); persister.insert( id, getState(), instance, session ); - PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); final EntityEntry entry = persistenceContext.getEntry( instance ); if ( entry == null ) { throw new AssertionFailure( "possible non-threadsafe access to session" ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/AttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/AttributeMapping.java index c52d20fb1564..963928a34577 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/AttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/AttributeMapping.java @@ -6,6 +6,7 @@ */ package org.hibernate.metamodel.mapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.sql.results.graph.DatabaseSnapshotContributor; import org.hibernate.sql.results.graph.Fetchable; @@ -92,4 +93,30 @@ default AttributeMapping asAttributeMapping() { return this; } + /** + * A utility method to avoid casting explicitly to PluralAttributeMapping + * + * @return PluralAttributeMapping if this is an instance of PluralAttributeMapping otherwise {@code null} + */ + default PluralAttributeMapping asPluralAttributeMapping() { + return null; + } + + default boolean isPluralAttributeMapping() { + return false; + } + + /** + * A utility method to avoid casting explicitly to EmbeddedAttributeMapping + * + * @return EmbeddedAttributeMapping if this is an instance of EmbeddedAttributeMapping otherwise {@code null} + */ + default EmbeddedAttributeMapping asEmbeddedAttributeMapping(){ + return null; + } + + default boolean isEmbeddedAttributeMapping(){ + return false; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java index b5365aba6e07..1c5bdad28052 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java @@ -175,4 +175,14 @@ default void applyWhereRestrictions( SqlAstCreationState creationState) { getCollectionDescriptor().applyWhereRestrictions( predicateConsumer, tableGroup, useQualifier, creationState ); } + + @Override + default PluralAttributeMapping asPluralAttributeMapping() { + return this; + } + + @Override + default boolean isPluralAttributeMapping() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java index 408f90706cc1..191fbf27b059 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java @@ -437,4 +437,14 @@ private static PropertyAccess getPropertyAccess( } return parentInjectionAttributePropertyAccess; } + + @Override + public EmbeddedAttributeMapping asEmbeddedAttributeMapping() { + return this; + } + + @Override + public boolean isEmbeddedAttributeMapping() { + return true; + } } From d1ffaf47b1caba70c3bf0c4caf7f86b89e2f93c7 Mon Sep 17 00:00:00 2001 From: Tomas Cerskus Date: Sat, 4 Feb 2023 19:17:32 +0000 Subject: [PATCH 0034/1497] HHH-15707 - Fix Gradle plugin with Kotlin 1.7.0 or higher Since Kotlin version 1.7.0 the KotlinCompile task no longer extends Gradle's AbstractCompile. This commit updates Hibernate Gradle enhancement plugin to not cast to AbstractCompile and instead use reflection to invoke the "getDestinationDirectory" method. It also updates the Kotlin version on used to test the Gradle enhancement (but remains backwards compatible with previous Kotlin versions). --- .../tooling/gradle/HibernateOrmPlugin.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmPlugin.java b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmPlugin.java index c5f68dac7df2..7b1ec99322db 100644 --- a/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmPlugin.java +++ b/tooling/hibernate-gradle-plugin/src/main/java/org/hibernate/orm/tooling/gradle/HibernateOrmPlugin.java @@ -6,6 +6,7 @@ */ package org.hibernate.orm.tooling.gradle; +import java.lang.reflect.Method; import java.util.Set; import org.gradle.api.Action; @@ -69,21 +70,26 @@ private void prepareEnhancement(HibernateOrmSpec ormDsl, Project project) { for ( String language : languages ) { final String languageCompileTaskName = sourceSet.getCompileTaskName( language ); - final AbstractCompile languageCompileTask = (AbstractCompile) project.getTasks().findByName( languageCompileTaskName ); + final Task languageCompileTask = project.getTasks().findByName( languageCompileTaskName ); if ( languageCompileTask == null ) { continue; } //noinspection Convert2Lambda - languageCompileTask.doLast( new Action<>() { + languageCompileTask.doLast(new Action<>() { @Override public void execute(Task t) { - final DirectoryProperty classesDirectory = languageCompileTask.getDestinationDirectory(); - final ClassLoader classLoader = Helper.toClassLoader( sourceSet, project ); - - EnhancementHelper.enhance( classesDirectory, classLoader, ormDsl, project ); + try { + final Method getDestinationDirectory = languageCompileTask.getClass().getMethod("getDestinationDirectory"); + final DirectoryProperty classesDirectory = (DirectoryProperty) getDestinationDirectory.invoke(languageCompileTask); + final ClassLoader classLoader = Helper.toClassLoader(sourceSet, project); + EnhancementHelper.enhance(classesDirectory, classLoader, ormDsl, project); + } + catch (Exception e) { + throw new RuntimeException(e); + } } - } ); + }); } } ); } From 1bb6fcfe1c1e5aef62719b738ba559574666cd8e Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 24 Nov 2022 16:48:42 +0100 Subject: [PATCH 0035/1497] HHH-15733 Add test for issue --- .../map/MapElementBaseTypeConversionTest.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/map/MapElementBaseTypeConversionTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/map/MapElementBaseTypeConversionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/map/MapElementBaseTypeConversionTest.java new file mode 100644 index 000000000000..7e9d1fc714bb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/map/MapElementBaseTypeConversionTest.java @@ -0,0 +1,97 @@ +/* + * 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.orm.test.mapping.converted.converter.map; + +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Convert; +import jakarta.persistence.Converter; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@DomainModel(annotatedClasses = { + MapElementBaseTypeConversionTest.Customer.class +}) +@SessionFactory +@JiraKey("HHH-15733") +public class MapElementBaseTypeConversionTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + Customer customer = new Customer(); + customer.getColors().put( "eyes", "blue" ); + session.persist( customer ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from Customer" ).executeUpdate() ); + } + + @Test + public void testBasicElementCollectionConversion(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + Customer customer = session.find( Customer.class, 1 ); + assertEquals( 1, customer.getColors().size() ); + assertEquals( "blue", customer.getColors().get( "eyes" ) ); + } ); + } + + @Entity(name = "Customer") + public static class Customer { + @Id + @GeneratedValue + private Integer id; + + @ElementCollection(fetch = FetchType.EAGER) + @Convert(converter = MyStringConverter.class) + private final Map colors = new HashMap<>(); + + public Customer() { + } + + public Customer(Integer id) { + this.id = id; + } + + public Map getColors() { + return colors; + } + } + + @Converter + public static class MyStringConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(String attribute) { + return new StringBuilder( attribute ).reverse().toString(); + } + + @Override + public String convertToEntityAttribute(String dbData) { + return new StringBuilder( dbData ).reverse().toString(); + } + } +} From f4e95d96c6ade861749d73629beb626295fe40ff Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 28 Nov 2022 19:06:14 +0100 Subject: [PATCH 0036/1497] HHH-15733 Change convert logic to default to value for Map collections of basic types --- .../boot/model/internal/CollectionBinder.java | 8 ++-- .../internal/CollectionPropertyHolder.java | 43 +++++++++++++------ .../boot/model/internal/MapBinder.java | 3 +- .../map/MapElementBaseTypeConversionTest.java | 9 +--- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java index 1cab0abd7eaa..27d9d6a4388c 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java @@ -2131,15 +2131,17 @@ private void handleElementCollection(XClass elementType, String hqlOrderBy) { propertyHolder, buildingContext ); - holder.prepare( property ); final Class> compositeUserType = resolveCompositeUserType( property, elementClass, buildingContext ); - if ( classType == EMBEDDABLE || compositeUserType != null ) { + boolean isComposite = classType == EMBEDDABLE || compositeUserType != null; + holder.prepare( property, isComposite ); + + if ( isComposite ) { handleCompositeCollectionElement( hqlOrderBy, elementClass, holder, compositeUserType ); } else { - handleCollectionElement( elementType, hqlOrderBy, elementClass, holder ); + handleCollectionElement( elementType, hqlOrderBy, elementClass, holder ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionPropertyHolder.java index 8e743d228dec..5a1bccefb7c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionPropertyHolder.java @@ -78,6 +78,7 @@ public Collection getCollectionBinding() { private void buildAttributeConversionInfoMaps( XProperty collectionProperty, + boolean isComposite, Map elementAttributeConversionInfoMap, Map keyAttributeConversionInfoMap) { if ( collectionProperty == null ) { @@ -88,7 +89,13 @@ private void buildAttributeConversionInfoMaps( { final Convert convertAnnotation = collectionProperty.getAnnotation( Convert.class ); if ( convertAnnotation != null ) { - applyLocalConvert( convertAnnotation, collectionProperty, elementAttributeConversionInfoMap, keyAttributeConversionInfoMap ); + applyLocalConvert( + convertAnnotation, + collectionProperty, + isComposite, + elementAttributeConversionInfoMap, + keyAttributeConversionInfoMap + ); } } @@ -99,6 +106,7 @@ private void buildAttributeConversionInfoMaps( applyLocalConvert( convertAnnotation, collectionProperty, + isComposite, elementAttributeConversionInfoMap, keyAttributeConversionInfoMap ); @@ -110,14 +118,15 @@ private void buildAttributeConversionInfoMaps( private void applyLocalConvert( Convert convertAnnotation, XProperty collectionProperty, + boolean isComposite, Map elementAttributeConversionInfoMap, Map keyAttributeConversionInfoMap) { - // IMPL NOTE : the rules here are quite more lenient than what JPA says. For example, JPA says - // that @Convert on a Map always needs to specify attributeName of key/value (or prefixed with - // key./value. for embedded paths). However, we try to see if conversion of either is disabled - // for whatever reason. For example, if the Map is annotated with @Enumerated the elements cannot - // be converted so any @Convert likely meant the key, so we apply it to the key + // IMPL NOTE : the rules here are quite more lenient than what JPA says. For example, JPA says that @Convert + // on a Map of basic types should default to "value" but it should explicitly specify attributeName of "key" + // (or prefixed with "key." for embedded paths) to be applied on the key. However, we try to see if conversion + // of either is disabled for whatever reason. For example, if the Map is annotated with @Enumerated the + // elements cannot be converted so any @Convert likely meant the key, so we apply it to the key final AttributeConversionInfo info = new AttributeConversionInfo( convertAnnotation, collectionProperty ); if ( collection.isMap() ) { @@ -132,10 +141,15 @@ private void applyLocalConvert( if ( isEmpty( info.getAttributeName() ) ) { // the @Convert did not name an attribute... if ( canElementBeConverted && canKeyBeConverted ) { - throw new IllegalStateException( - "@Convert placed on Map attribute [" + collection.getRole() - + "] must define attributeName of 'key' or 'value'" - ); + if ( !isComposite ) { + // if element is of basic type default to "value" + elementAttributeConversionInfoMap.put( "", info ); + } + else { + throw new IllegalStateException( + "@Convert placed on Map attribute [" + collection.getRole() + + "] of non-basic types must define attributeName of 'key' or 'value'" ); + } } else if ( canKeyBeConverted ) { keyAttributeConversionInfoMap.put( "", info ); @@ -325,7 +339,7 @@ public String toString() { boolean prepared; - public void prepare(XProperty collectionProperty) { + public void prepare(XProperty collectionProperty, boolean isComposite) { // fugly if ( prepared ) { return; @@ -377,7 +391,12 @@ else if ( collectionProperty.isAnnotationPresent( CollectionType.class ) ) { // Is it valid to reference a collection attribute in a @Convert attached to the owner (entity) by path? // if so we should pass in 'clazzToProcess' also if ( canKeyBeConverted || canElementBeConverted ) { - buildAttributeConversionInfoMaps( collectionProperty, elementAttributeConversionInfoMap, keyAttributeConversionInfoMap ); + buildAttributeConversionInfoMaps( + collectionProperty, + isComposite, + elementAttributeConversionInfoMap, + keyAttributeConversionInfoMap + ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java index 20f3ce78c710..aa12028a698a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/MapBinder.java @@ -37,6 +37,7 @@ import org.hibernate.mapping.Table; import org.hibernate.mapping.Value; import org.hibernate.resource.beans.spi.ManagedBean; +import org.hibernate.type.BasicType; import org.hibernate.usertype.CompositeUserType; import org.hibernate.usertype.UserCollectionType; @@ -278,7 +279,7 @@ private CollectionPropertyHolder buildCollectionPropertyHolder( // 'holder' is the CollectionPropertyHolder. // 'property' is the collection XProperty propertyHolder.startingProperty( property ); - holder.prepare(property); + holder.prepare( property, !( collection.getKey().getType() instanceof BasicType ) ); return holder; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/map/MapElementBaseTypeConversionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/map/MapElementBaseTypeConversionTest.java index 7e9d1fc714bb..18f150b7879d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/map/MapElementBaseTypeConversionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/map/MapElementBaseTypeConversionTest.java @@ -67,16 +67,9 @@ public static class Customer { private Integer id; @ElementCollection(fetch = FetchType.EAGER) - @Convert(converter = MyStringConverter.class) + @Convert(converter = MyStringConverter.class) // note omitted attributeName private final Map colors = new HashMap<>(); - public Customer() { - } - - public Customer(Integer id) { - this.id = id; - } - public Map getColors() { return colors; } From ae238d303271f1b613e3c59c9fa1f7209c5b192f Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 2 Feb 2023 12:23:40 -0600 Subject: [PATCH 0037/1497] HHH-16081 - Converted collection-as-basic values are considered immutable HHH-16132 - Dirty checking broken for collection-as-basic mappings (test) --- .../internal/NamedConverterResolution.java | 58 ++++-- .../util/collections/CollectionHelper.java | 10 + .../java/spi/CollectionJavaType.java | 40 +++- .../descriptor/java/spi/JavaTypeRegistry.java | 2 +- .../mutabiity/ConvertedMapImmutableTests.java | 165 +++++++++++++++ .../mutabiity/ConvertedMapMutableTests.java | 179 +++++++++++++++++ .../mutabiity/ConvertedMutabilityTests.java | 188 ++++++++++++++++++ 7 files changed, 613 insertions(+), 29 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapImmutableTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapMutableTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMutabilityTests.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java index e423fe78fcba..66d8b6ab605b 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/process/internal/NamedConverterResolution.java @@ -6,18 +6,21 @@ */ package org.hibernate.boot.model.process.internal; +import java.util.Map; import java.util.function.Function; +import org.hibernate.annotations.Immutable; import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor; import org.hibernate.boot.model.convert.spi.ConverterDescriptor; import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Collection; import org.hibernate.metamodel.mapping.JdbcMapping; -import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter; import org.hibernate.type.BasicType; import org.hibernate.type.descriptor.converter.internal.AttributeConverterMutabilityPlanImpl; +import org.hibernate.type.descriptor.converter.spi.JpaAttributeConverter; import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaType; @@ -112,23 +115,14 @@ private static NamedConverterResolution fromInternal( ? explicitJdbcType : relationalJtd.getRecommendedJdbcType( sqlTypeIndicators ); - final MutabilityPlan explicitMutabilityPlan = explicitMutabilityPlanAccess != null - ? explicitMutabilityPlanAccess.apply( typeConfiguration ) - : null; - - - final MutabilityPlan mutabilityPlan; - if ( explicitMutabilityPlan != null ) { - mutabilityPlan = explicitMutabilityPlan; - } - else if ( ! domainJtd.getMutabilityPlan().isMutable() ) { - mutabilityPlan = ImmutableMutabilityPlan.instance(); - } - else { - mutabilityPlan = new AttributeConverterMutabilityPlanImpl<>( converter, true ); - } + final MutabilityPlan mutabilityPlan = determineMutabilityPlan( + explicitMutabilityPlanAccess, + typeConfiguration, + converter, + domainJtd + ); - return new NamedConverterResolution( + return new NamedConverterResolution<>( domainJtd, relationalJtd, jdbcType, @@ -138,6 +132,36 @@ else if ( ! domainJtd.getMutabilityPlan().isMutable() ) { ); } + private static MutabilityPlan determineMutabilityPlan( + Function explicitMutabilityPlanAccess, + TypeConfiguration typeConfiguration, + JpaAttributeConverter converter, + JavaType domainJtd) { + //noinspection unchecked + final MutabilityPlan explicitMutabilityPlan = explicitMutabilityPlanAccess != null + ? explicitMutabilityPlanAccess.apply( typeConfiguration ) + : null; + if ( explicitMutabilityPlan != null ) { + return explicitMutabilityPlan; + } + + if ( converter.getConverterJavaType().getJavaTypeClass().isAnnotationPresent( Immutable.class ) ) { + return ImmutableMutabilityPlan.instance(); + } + + if ( !domainJtd.getMutabilityPlan().isMutable() + && !isCollection( domainJtd.getJavaTypeClass() ) ) { + return ImmutableMutabilityPlan.instance(); + } + + return new AttributeConverterMutabilityPlanImpl<>( converter, true ); + } + + private static boolean isCollection(Class javaType) { + return Collection.class.isAssignableFrom( javaType ) + || Map.class.isAssignableFrom( javaType ); + } + private final JavaType domainJtd; private final JavaType relationalJtd; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java index 927784930db1..00a13f9dbdb0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java @@ -426,6 +426,16 @@ private static void applyToMap(Map map, Object... pairs) { } } + public static String[] asPairs(Map map) { + final String[] pairs = new String[ map.size() * 2 ]; + int i = 0; + for ( Map.Entry entry : map.entrySet() ) { + pairs[i++] = entry.getKey(); + pairs[i++] = entry.getValue(); + } + return pairs; + } + public static Properties toProperties(Object... pairs) { final Properties properties = new Properties(); if ( pairs.length > 0 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/CollectionJavaType.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/CollectionJavaType.java index fd8dbebe7c58..c4a8d31dbee2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/CollectionJavaType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/CollectionJavaType.java @@ -7,6 +7,7 @@ package org.hibernate.type.descriptor.java.spi; import java.lang.reflect.ParameterizedType; +import java.util.Objects; import org.hibernate.collection.spi.CollectionSemantics; import org.hibernate.collection.spi.PersistentCollection; @@ -89,17 +90,34 @@ public C wrap(X value, WrapperOptions options) { @Override public boolean areEqual(C one, C another) { - return one == another || - ( - one instanceof PersistentCollection && - ( (PersistentCollection) one ).wasInitialized() && - ( (PersistentCollection) one ).isWrapper( another ) - ) || - ( - another instanceof PersistentCollection && - ( (PersistentCollection) another ).wasInitialized() && - ( (PersistentCollection) another ).isWrapper( one ) - ); +// return one == another || +// ( +// one instanceof PersistentCollection && +// ( (PersistentCollection) one ).wasInitialized() && +// ( (PersistentCollection) one ).isWrapper( another ) +// ) || +// ( +// another instanceof PersistentCollection && +// ( (PersistentCollection) another ).wasInitialized() && +// ( (PersistentCollection) another ).isWrapper( one ) +// ); + + + if ( one == another ) { + return true; + } + + if ( one instanceof PersistentCollection ) { + final PersistentCollection pc = (PersistentCollection) one; + return pc.wasInitialized() && ( pc.isWrapper( another ) || pc.isDirectlyProvidedCollection( another ) ); + } + + if ( another instanceof PersistentCollection ) { + final PersistentCollection pc = (PersistentCollection) another; + return pc.wasInitialized() && ( pc.isWrapper( one ) || pc.isDirectlyProvidedCollection( one ) ); + } + + return Objects.equals( one, another ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeRegistry.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeRegistry.java index 8be9a45e1478..122207dae310 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeRegistry.java @@ -136,7 +136,7 @@ public JavaType resolveDescriptor(Type javaType) { () -> { if ( javaType instanceof ParameterizedType ) { final ParameterizedType parameterizedType = (ParameterizedType) javaType; - final JavaType rawType = findDescriptor( ( parameterizedType ).getRawType() ); + final JavaType rawType = findDescriptor( parameterizedType.getRawType() ); if ( rawType != null ) { return rawType.createJavaType( parameterizedType, typeConfiguration ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapImmutableTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapImmutableTests.java new file mode 100644 index 000000000000..152487fe3a7d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapImmutableTests.java @@ -0,0 +1,165 @@ +/* + * 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.orm.test.mapping.converted.converter.mutabiity; + +import java.util.Map; + +import org.hibernate.annotations.Immutable; +import org.hibernate.internal.util.collections.CollectionHelper; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = ConvertedMapImmutableTests.TestEntity.class ) +@SessionFactory( useCollectingStatementInspector = true ) +public class ConvertedMapImmutableTests { + + @Test + @JiraKey( "HHH-16081" ) + void testManagedUpdate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + scope.inTransaction( (session) -> { + final TestEntity loaded = session.get( TestEntity.class, 1 ); + loaded.values.put( "ghi", "789" ); + statementInspector.clear(); + } ); + + final TestEntity after = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( after.values ).hasSize( 2 ); + } + + @Test + @JiraKey( "HHH-16081" ) + @FailureExpected( reason = "Fails due to HHH-16132 - Hibernate believes the attribute is dirty, even though it is immutable." ) + void testMerge(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( loaded.values ).hasSize( 2 ); + + loaded.values.put( "ghi", "789" ); + statementInspector.clear(); + scope.inTransaction( (session) -> session.merge( loaded ) ); + + final TestEntity merged = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( merged.values ).hasSize( 2 ); + } + + @Test + @JiraKey( "HHH-16132" ) + @FailureExpected( reason = "Fails due to HHH-16132 - Hibernate believes the attribute is dirty, even though it is immutable." ) + void testDirtyChecking(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // make changes to a managed entity - should not trigger update since it is immutable + scope.inTransaction( (session) -> { + final TestEntity managed = session.get( TestEntity.class, 1 ); + statementInspector.clear(); + assertThat( managed.values ).hasSize( 2 ); + // make the change + managed.values.put( "ghi", "789" ); + } ); + assertThat( statementInspector.getSqlQueries() ).isEmpty(); + + // make no changes to a detached entity and merge it - should not trigger update + final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( loaded.values ).hasSize( 2 ); + // make the change + loaded.values.put( "ghi", "789" ); + statementInspector.clear(); + scope.inTransaction( (session) -> session.merge( loaded ) ); + // the SELECT + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + } + + @Test + @JiraKey( "HHH-16132" ) + void testNotDirtyChecking(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // make changes to a managed entity - should not trigger update + scope.inTransaction( (session) -> { + final TestEntity managed = session.get( TestEntity.class, 1 ); + statementInspector.clear(); + assertThat( managed.values ).hasSize( 2 ); + } ); + assertThat( statementInspector.getSqlQueries() ).isEmpty(); + + // make no changes to a detached entity and merge it - should not trigger update + final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( loaded.values ).hasSize( 2 ); + statementInspector.clear(); + scope.inTransaction( (session) -> session.merge( loaded ) ); + // the SELECT + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + } + + @BeforeEach + void createTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TestEntity( + 1, + CollectionHelper.toMap( + "abc", "123", + "def", "456" + ) + ) ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.createMutationQuery( "delete TestEntity" ).executeUpdate(); + } ); + } + + @Immutable + public static class ImmutableMapConverter extends ConvertedMapMutableTests.MapConverter { + } + + @Entity( name = "TestEntity" ) + @Table( name = "entity_immutable_map" ) + public static class TestEntity { + @Id + private Integer id; + + @Convert( converter = ImmutableMapConverter.class ) + @Column( name="vals" ) + private Map values; + + private TestEntity() { + // for use by Hibernate + } + + public TestEntity( + Integer id, + Map values) { + this.id = id; + this.values = values; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapMutableTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapMutableTests.java new file mode 100644 index 000000000000..22cc1f6e934f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapMutableTests.java @@ -0,0 +1,179 @@ +/* + * 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.orm.test.mapping.converted.converter.mutabiity; + +import java.util.Map; + +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.CollectionHelper; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Ebersole + */ +@JiraKey( "HHH-16081" ) +@DomainModel( annotatedClasses = ConvertedMapMutableTests.TestEntity.class ) +@SessionFactory( useCollectingStatementInspector = true ) +public class ConvertedMapMutableTests { + + @Test + void testMutableMap(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + scope.inTransaction( (session) -> { + final TestEntity loaded = session.get( TestEntity.class, 1 ); + assertThat( loaded.values ).hasSize( 2 ); + loaded.values.put( "ghi", "789" ); + statementInspector.clear(); + } ); + assertThat( statementInspector.getSqlQueries() ).isNotEmpty(); + + scope.inTransaction( (session) -> { + final TestEntity loaded = session.get( TestEntity.class, 1 ); + assertThat( loaded.values ).hasSize( 3 ); + statementInspector.clear(); + } ); + assertThat( statementInspector.getSqlQueries() ).isEmpty(); + } + + @Test + void testMutableMapWithMerge(SessionFactoryScope scope) { + final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( loaded.values ).hasSize( 2 ); + + loaded.values.put( "ghi", "789" ); + scope.inTransaction( (session) -> session.merge( loaded ) ); + + final TestEntity changed = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( changed.values ).hasSize( 3 ); + } + + @Test + @JiraKey( "HHH-16132" ) + void testDirtyChecking(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // make changes to a managed entity - should trigger update + scope.inTransaction( (session) -> { + final TestEntity managed = session.get( TestEntity.class, 1 ); + statementInspector.clear(); + assertThat( managed.values ).hasSize( 2 ); + // make the change + managed.values.put( "ghi", "789" ); + } ); + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + + // make changes to a detached entity and merge it - should trigger update + final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( loaded.values ).hasSize( 3 ); + // make the change + loaded.values.put( "jkl", "007" ); + statementInspector.clear(); + scope.inTransaction( (session) -> session.merge( loaded ) ); + // the SELECT + UPDATE + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + } + + @Test + @JiraKey( "HHH-16132" ) + void testNotDirtyChecking(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // make no changes to a managed entity - should not trigger update + scope.inTransaction( (session) -> { + final TestEntity managed = session.get( TestEntity.class, 1 ); + statementInspector.clear(); + assertThat( managed.values ).hasSize( 2 ); + } ); + assertThat( statementInspector.getSqlQueries() ).isEmpty(); + + // make no changes to a detached entity and merge it - should not trigger update + final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( loaded.values ).hasSize( 2 ); + statementInspector.clear(); + scope.inTransaction( (session) -> session.merge( loaded ) ); + // the SELECT + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + } + + @BeforeEach + void createTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TestEntity( + 1, + CollectionHelper.toMap( + "abc", "123", + "def", "456" + ) + ) ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.createMutationQuery( "delete TestEntity" ).executeUpdate(); + } ); + } + + public static class MapConverter implements AttributeConverter,String> { + @Override + public String convertToDatabaseColumn(Map map) { + if ( CollectionHelper.isEmpty( map ) ) { + return null; + } + return StringHelper.join( ", ", CollectionHelper.asPairs( map ) ); + } + + @Override + public Map convertToEntityAttribute(String pairs) { + if ( StringHelper.isEmpty( pairs ) ) { + return null; + } + return CollectionHelper.toMap( StringHelper.split( ", ", pairs ) ); + } + } + + @Entity( name = "TestEntity" ) + @Table( name = "entity_mutable_map" ) + public static class TestEntity { + @Id + private Integer id; + + @Convert( converter = MapConverter.class ) + @Column( name = "vals" ) + private Map values; + + private TestEntity() { + // for use by Hibernate + } + + public TestEntity( + Integer id, + Map values) { + this.id = id; + this.values = values; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMutabilityTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMutabilityTests.java new file mode 100644 index 000000000000..70b70706c3e8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMutabilityTests.java @@ -0,0 +1,188 @@ +/* + * 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.orm.test.mapping.converted.converter.mutabiity; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +import org.hibernate.annotations.Immutable; +import org.hibernate.internal.util.StringHelper; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Ebersole + */ +@JiraKey( "HHH-16081" ) +@DomainModel( annotatedClasses = ConvertedMutabilityTests.TestEntityWithDates.class ) +@SessionFactory( useCollectingStatementInspector = true ) +public class ConvertedMutabilityTests { + private static final Instant START = Instant.now(); + + @Test + void testImmutableDate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + scope.inTransaction( (session) -> { + final TestEntityWithDates loaded = session.get( TestEntityWithDates.class, 1 ); + + statementInspector.clear(); + + // change `d2` - because it is immutable, this should not trigger an update + loaded.d2.setTime( Instant.EPOCH.toEpochMilli() ); + } ); + + assertThat( statementInspector.getSqlQueries() ).isEmpty(); + + scope.inTransaction( (session) -> { + final TestEntityWithDates loaded = session.get( TestEntityWithDates.class, 1 ); + assertThat( loaded.d1.getTime() ).isEqualTo( START.toEpochMilli() ); + } ); + } + + @Test + void testMutableDate(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + scope.inTransaction( (session) -> { + final TestEntityWithDates loaded = session.get( TestEntityWithDates.class, 1 ); + + statementInspector.clear(); + + // change `d1` - because it is mutable, this should trigger an update + loaded.d1.setTime( Instant.EPOCH.toEpochMilli() ); + } ); + + assertThat( statementInspector.getSqlQueries() ).isNotEmpty(); + + scope.inTransaction( (session) -> { + final TestEntityWithDates loaded = session.get( TestEntityWithDates.class, 1 ); + assertThat( loaded.d1.getTime() ).isEqualTo( Instant.EPOCH.toEpochMilli() ); + } ); + } + + @Test + void testDatesWithMerge(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + final TestEntityWithDates loaded = scope.fromTransaction( (session) -> session.get( TestEntityWithDates.class, 1 ) ); + + loaded.d1.setTime( Instant.EPOCH.toEpochMilli() ); + + statementInspector.clear(); + scope.inTransaction( (session) -> session.merge( loaded ) ); + assertThat( statementInspector.getSqlQueries() ).isNotEmpty(); + + final TestEntityWithDates loaded2 = scope.fromTransaction( (session) -> session.get( TestEntityWithDates.class, 1 ) ); + assertThat( loaded2.d1.getTime() ).isEqualTo( Instant.EPOCH.toEpochMilli() ); + + loaded2.d2.setTime( Instant.EPOCH.toEpochMilli() ); + statementInspector.clear(); + scope.inTransaction( (session) -> session.merge( loaded ) ); + assertThat( statementInspector.getSqlQueries() ).isNotEmpty(); + + final TestEntityWithDates loaded3 = scope.fromTransaction( (session) -> session.get( TestEntityWithDates.class, 1 ) ); + assertThat( loaded3.d2.getTime() ).isEqualTo( START.toEpochMilli() ); + } + + @BeforeEach + void createTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TestEntityWithDates( + 1, + Date.from( START ), + Date.from( START ) + ) ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.createMutationQuery( "delete TestEntityWithDates" ).executeUpdate(); + } ); + } + + public static class DateConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(Date date) { + if ( date == null ) { + return null; + } + return DateTimeFormatter.ISO_INSTANT.format( date.toInstant() ); + } + + @Override + public Date convertToEntityAttribute(String date) { + if ( StringHelper.isEmpty( date ) ) { + return null; + } + return Date.from( Instant.from( DateTimeFormatter.ISO_INSTANT.parse( date ) ) ); + } + } + + @Immutable + public static class ImmutableDateConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(Date date) { + if ( date == null ) { + return null; + } + return DateTimeFormatter.ISO_INSTANT.format( date.toInstant() ); + } + + @Override + public Date convertToEntityAttribute(String date) { + if ( StringHelper.isEmpty( date ) ) { + return null; + } + return Date.from( Instant.from( DateTimeFormatter.ISO_INSTANT.parse( date ) ) ); + } + } + + + @Entity( name = "TestEntityWithDates" ) + @Table( name = "entity_dates" ) + public static class TestEntityWithDates { + @Id + private Integer id; + + @Convert( converter = DateConverter.class ) + private Date d1; + @Convert( converter = ImmutableDateConverter.class ) + private Date d2; + + private TestEntityWithDates() { + // for use by Hibernate + } + + public TestEntityWithDates( + Integer id, + Date d1, + Date d2) { + this.id = id; + this.d1 = d1; + this.d2 = d2; + } + } + +} From 6861290978456a2fb2502c1ef50f67d41920e515 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 6 Feb 2023 10:10:14 +0100 Subject: [PATCH 0038/1497] Fix tests for Oracle 11 --- .../orm/test/annotations/id/IdClassAndAssociationsTest.java | 2 +- .../EmbeddablePersistAndQueryInSameTransactionTest.java | 4 ++-- .../orm/test/polymorphic/PolymorphicQueriesWithJoinTest.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java index c3a4d42f1bce..bb5de187e982 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/id/IdClassAndAssociationsTest.java @@ -87,7 +87,7 @@ public void testIt(EntityManagerFactoryScope scope) { @Table(name = "course_enrollment") public static class CourseEnrollment { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name = "course") diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/EmbeddablePersistAndQueryInSameTransactionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/EmbeddablePersistAndQueryInSameTransactionTest.java index baa90452d330..b4234c7891a2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/EmbeddablePersistAndQueryInSameTransactionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/EmbeddablePersistAndQueryInSameTransactionTest.java @@ -59,7 +59,7 @@ public void testIt(EntityManagerFactoryScope scope) { public static class Child { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Basic @@ -86,7 +86,7 @@ public String getName() { public static class Dog { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Basic diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/polymorphic/PolymorphicQueriesWithJoinTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/polymorphic/PolymorphicQueriesWithJoinTest.java index 28e810ad238e..59e42123fc91 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/polymorphic/PolymorphicQueriesWithJoinTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/polymorphic/PolymorphicQueriesWithJoinTest.java @@ -144,7 +144,7 @@ public interface Animal { public static class Cat implements Animal { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; @@ -169,7 +169,7 @@ public void addToOwners(Person person) { public static class Dog implements Animal { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; From a7448bb15a016cf6310a26c812c2056a48f49429 Mon Sep 17 00:00:00 2001 From: Markus Heiden Date: Fri, 3 Feb 2023 12:00:24 +0100 Subject: [PATCH 0039/1497] [HHH-16122] Add test to reproduce problem --- .../orm/test/annotations/HHH16122Test.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/HHH16122Test.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/HHH16122Test.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/HHH16122Test.java new file mode 100644 index 000000000000..8a1e96fd2c56 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/HHH16122Test.java @@ -0,0 +1,58 @@ +package org.hibernate.orm.test.annotations; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +@TestForIssue( jiraKey = "HHH-16122" ) +public class HHH16122Test extends BaseEntityManagerFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { ValueConverter.class, SuperClass.class, SubClass.class }; + } + + @Test + public void testGenericSuperClassWithConverter() { + // The test is successful if the entity manager factory can be built. + } + + public static class ConvertedValue { + public final long value; + public ConvertedValue(long value) { + this.value = value; + } + } + + @Converter(autoApply = true) + public static class ValueConverter implements AttributeConverter { + @Override + public Long convertToDatabaseColumn( ConvertedValue value ) { + return value.value; + } + @Override + public ConvertedValue convertToEntityAttribute( Long value ) { + return new ConvertedValue(value); + } + } + + @MappedSuperclass + public static abstract class SuperClass { + @Id + private String id; + public ConvertedValue convertedValue = new ConvertedValue( 1 ); + public ConvertedValue getConvertedValue() { + return convertedValue; + } + public void setConvertedValue(ConvertedValue convertedValue) { + this.convertedValue = convertedValue; + } + } + + @Entity(name = "SubClass") + public static class SubClass extends SuperClass {} +} From 2271e18ba57ef02d589be839619e10f7c878b9f0 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 7 Feb 2023 13:43:41 +0100 Subject: [PATCH 0040/1497] [HHH-16122] Fix determining the type of a property during type variable resolving for mapped superclasses --- .../hibernate/boot/model/internal/ClassPropertyHolder.java | 7 ++++++- .../org/hibernate/boot/model/internal/PropertyBinder.java | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java index 703efb187699..5be0e8c1d6b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ClassPropertyHolder.java @@ -269,7 +269,7 @@ private void addPropertyToMappedSuperclass(Property prop, XClass declaringClass) final Value originalValue = prop.getValue(); if ( originalValue instanceof SimpleValue ) { // Avoid copying when the property doesn't depend on a type variable - if ( inferredData.getTypeName().equals( getTypeName( originalValue ) ) ) { + if ( inferredData.getTypeName().equals( getTypeName( prop ) ) ) { superclass.addDeclaredProperty( prop ); return; } @@ -329,6 +329,11 @@ public void doSecondPass(Map persistentClasses) throws MappingException { } } + static String getTypeName(Property property) { + final String typeName = getTypeName( property.getValue() ); + return typeName != null ? typeName : property.getReturnedClassName(); + } + static String getTypeName(Value value) { if ( value instanceof Component ) { final Component component = (Component) value; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java index 2d418329e545..43bd70b1f632 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java @@ -365,7 +365,7 @@ private void setDeclaredIdentifier(RootClass rootClass, MappedSuperclass supercl final Value originalValue = prop.getValue(); if ( originalValue instanceof SimpleValue ) { // Avoid copying when the property doesn't depend on a type variable - if ( inferredData.getTypeName().equals( ClassPropertyHolder.getTypeName( originalValue ) ) ) { + if ( inferredData.getTypeName().equals( ClassPropertyHolder.getTypeName( prop ) ) ) { superclass.setDeclaredIdentifierProperty( prop ); return; } From a71e26e3336f7d1a3d6ac0fcfb6551b252e1ab21 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 11 Jan 2023 12:50:57 +0100 Subject: [PATCH 0041/1497] HHH-15875 Fix join fetch support for associations within embedded ids --- .../internal/ToOneAttributeMapping.java | 74 ++--- .../internal/SingularAttributeImpl.java | 19 ++ .../org/hibernate/query/sqm/SqmJoinable.java | 8 +- .../query/sqm/spi/SqmCreationHelper.java | 2 +- .../sqm/sql/BaseSqmToSqlAstConverter.java | 28 +- .../tree/domain/AbstractSqmAttributeJoin.java | 2 +- .../spi/EntityIdentifierNavigablePath.java | 5 + .../sql/results/graph/FetchParent.java | 6 +- .../entity/internal/EntityResultImpl.java | 2 +- ...StandardEntityGraphTraversalStateImpl.java | 4 + .../orm/test/jpa/cdi/FetchEmbeddedIdTest.java | 271 ++++++++++++++++++ 11 files changed, 365 insertions(+), 56 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/cdi/FetchEmbeddedIdTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java index 89b4a48424b5..7a3e40b8b394 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java @@ -362,7 +362,6 @@ the navigable path is NavigablePath(Card.fields.{element}.{id}.card) and it does isInternalLoadNullable = isNullable(); } - if ( referencedPropertyName == null ) { final Set targetKeyPropertyNames = new HashSet<>( 2 ); targetKeyPropertyNames.add( EntityIdentifierMapping.ROLE_LOCAL_NAME ); @@ -380,18 +379,12 @@ the navigable path is NavigablePath(Card.fields.{element}.{id}.card) and it does if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded() && compositeType.getPropertyNames().length == 1 ) { this.targetKeyPropertyName = compositeType.getPropertyNames()[0]; - addPrefixedPropertyNames( + addPrefixedPropertyPaths( targetKeyPropertyNames, targetKeyPropertyName, compositeType.getSubtypes()[0], declaringEntityPersister.getFactory() ); - addPrefixedPropertyNames( - targetKeyPropertyNames, - ForeignKeyDescriptor.PART_NAME, - compositeType.getSubtypes()[0], - declaringEntityPersister.getFactory() - ); } else { this.targetKeyPropertyName = EntityIdentifierMapping.ROLE_LOCAL_NAME; @@ -401,52 +394,34 @@ the navigable path is NavigablePath(Card.fields.{element}.{id}.card) and it does propertyType, declaringEntityPersister.getFactory() ); - addPrefixedPropertyNames( + addPrefixedPropertyPaths( targetKeyPropertyNames, targetKeyPropertyName, propertyType, declaringEntityPersister.getFactory() ); - addPrefixedPropertyNames( - targetKeyPropertyNames, - ForeignKeyDescriptor.PART_NAME, - propertyType, - declaringEntityPersister.getFactory() - ); } } else { this.targetKeyPropertyName = entityBinding.getIdentifierProperty().getName(); - addPrefixedPropertyNames( + addPrefixedPropertyPaths( targetKeyPropertyNames, targetKeyPropertyName, propertyType, declaringEntityPersister.getFactory() ); - addPrefixedPropertyNames( - targetKeyPropertyNames, - ForeignKeyDescriptor.PART_NAME, - propertyType, - declaringEntityPersister.getFactory() - ); } this.targetKeyPropertyNames = targetKeyPropertyNames; } else if ( bootValue.isReferenceToPrimaryKey() ) { this.targetKeyPropertyName = referencedPropertyName; final Set targetKeyPropertyNames = new HashSet<>( 2 ); - addPrefixedPropertyNames( + addPrefixedPropertyPaths( targetKeyPropertyNames, targetKeyPropertyName, bootValue.getType(), declaringEntityPersister.getFactory() ); - addPrefixedPropertyNames( - targetKeyPropertyNames, - ForeignKeyDescriptor.PART_NAME, - bootValue.getType(), - declaringEntityPersister.getFactory() - ); this.targetKeyPropertyNames = targetKeyPropertyNames; } else { @@ -458,18 +433,12 @@ else if ( bootValue.isReferenceToPrimaryKey() ) { && compositeType.getPropertyNames().length == 1 ) { final Set targetKeyPropertyNames = new HashSet<>( 2 ); this.targetKeyPropertyName = compositeType.getPropertyNames()[0]; - addPrefixedPropertyNames( + addPrefixedPropertyPaths( targetKeyPropertyNames, targetKeyPropertyName, compositeType.getSubtypes()[0], declaringEntityPersister.getFactory() ); - addPrefixedPropertyNames( - targetKeyPropertyNames, - ForeignKeyDescriptor.PART_NAME, - compositeType.getSubtypes()[0], - declaringEntityPersister.getFactory() - ); this.targetKeyPropertyNames = targetKeyPropertyNames; } else { @@ -480,18 +449,12 @@ else if ( bootValue.isReferenceToPrimaryKey() ) { if ( ( mapsIdAttributeName = findMapsIdPropertyName( entityMappingType, referencedPropertyName ) ) != null ) { final Set targetKeyPropertyNames = new HashSet<>( 2 ); targetKeyPropertyNames.add( targetKeyPropertyName ); - addPrefixedPropertyNames( + addPrefixedPropertyPaths( targetKeyPropertyNames, mapsIdAttributeName, entityMappingType.getEntityPersister().getIdentifierType(), declaringEntityPersister.getFactory() ); - addPrefixedPropertyNames( - targetKeyPropertyNames, - ForeignKeyDescriptor.PART_NAME, - entityMappingType.getEntityPersister().getIdentifierType(), - declaringEntityPersister.getFactory() - ); this.targetKeyPropertyNames = targetKeyPropertyNames; } else { @@ -666,6 +629,31 @@ static String findMapsIdPropertyName(EntityMappingType entityMappingType, String return null; } + private static void addPrefixedPropertyPaths( + Set targetKeyPropertyNames, + String prefix, + Type type, + SessionFactoryImplementor factory) { + addPrefixedPropertyNames( + targetKeyPropertyNames, + prefix, + type, + factory + ); + addPrefixedPropertyNames( + targetKeyPropertyNames, + ForeignKeyDescriptor.PART_NAME, + type, + factory + ); + addPrefixedPropertyNames( + targetKeyPropertyNames, + EntityIdentifierMapping.ROLE_LOCAL_NAME, + type, + factory + ); + } + public static void addPrefixedPropertyNames( Set targetKeyPropertyNames, String prefix, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java index 609de7b8e6a2..f00206fc1206 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/SingularAttributeImpl.java @@ -13,20 +13,25 @@ import org.hibernate.graph.spi.GraphHelper; import org.hibernate.metamodel.AttributeClassification; import org.hibernate.metamodel.internal.MetadataContext; +import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.model.domain.AnyMappingDomainType; import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.ManagedDomainType; +import org.hibernate.metamodel.model.domain.PluralPersistentAttribute; import org.hibernate.metamodel.model.domain.SimpleDomainType; import org.hibernate.metamodel.model.domain.SingularPersistentAttribute; import org.hibernate.query.SemanticException; import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.sqm.internal.SqmMappingModelHelper; +import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmJoinType; import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmSingularJoin; import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; import org.hibernate.query.sqm.tree.from.SqmFrom; +import org.hibernate.spi.EntityIdentifierNavigablePath; +import org.hibernate.spi.NavigablePath; import org.hibernate.type.descriptor.java.JavaType; /** @@ -162,6 +167,20 @@ public Identifier( metadataContext ); } + + @Override + public NavigablePath createNavigablePath(SqmPath parent, String alias) { + if ( parent == null ) { + throw new IllegalArgumentException( + "`lhs` cannot be null for a sub-navigable reference - " + parent + ); + } + NavigablePath navigablePath = parent.getNavigablePath(); + if ( parent.getReferencedPathSource() instanceof PluralPersistentAttribute ) { + navigablePath = navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ); + } + return new EntityIdentifierNavigablePath( navigablePath, SqmCreationHelper.determineAlias( alias ), getName() ); + } } /** diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmJoinable.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmJoinable.java index 2a46e4e4de78..f9e8b6ab6209 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmJoinable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/SqmJoinable.java @@ -7,10 +7,12 @@ package org.hibernate.query.sqm; import org.hibernate.query.hql.spi.SqmCreationState; +import org.hibernate.query.sqm.spi.SqmCreationHelper; import org.hibernate.query.sqm.tree.SqmJoinType; -import org.hibernate.query.sqm.tree.from.SqmAttributeJoin; +import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.from.SqmFrom; import org.hibernate.query.sqm.tree.from.SqmJoin; +import org.hibernate.spi.NavigablePath; /** * Specialization for attributes that that can be used in creating SQM joins @@ -29,4 +31,8 @@ SqmJoin createSqmJoin( SqmCreationState creationState); String getName(); + + default NavigablePath createNavigablePath(SqmPath parent, String alias) { + return SqmCreationHelper.buildSubNavigablePath( parent, getName(), alias ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java index ae3dadd08537..238450d18b78 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/spi/SqmCreationHelper.java @@ -35,7 +35,7 @@ public static NavigablePath buildSubNavigablePath(NavigablePath lhs, String base return lhs.append( base, determineAlias( alias ) ); } - private static String determineAlias(String alias) { + public static String determineAlias(String alias) { // Make sure we always create a unique alias, otherwise we might use a wrong table group for the same join if ( alias == null ) { return Long.toString( System.nanoTime() ); 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 d2e60f80329c..bff7b2130a4f 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 @@ -272,6 +272,7 @@ import org.hibernate.query.sqm.tree.update.SqmAssignment; import org.hibernate.query.sqm.tree.update.SqmSetClause; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; +import org.hibernate.spi.EntityIdentifierNavigablePath; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstJoinType; @@ -382,6 +383,7 @@ import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.FetchableContainer; +import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; import org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiation; import org.hibernate.sql.results.graph.internal.ImmutableFetchList; import org.hibernate.sql.results.internal.SqlSelectionImpl; @@ -7162,7 +7164,16 @@ public Object visitFullyQualifiedClass(Class namedClass) { // .getOrMakeJavaDescriptor( namedClass ); } - private void addFetch(ImmutableFetchList.Builder fetches, FetchParent fetchParent, Fetchable fetchable, Boolean isKeyFetchable) { + @Override + public Fetch visitIdentifierFetch(EntityResultGraphNode fetchParent) { + final EntityIdentifierMapping identifierMapping = fetchParent.getEntityValuedModelPart() + .getEntityMappingType() + .getIdentifierMapping(); + final Fetchable fetchableIdentifierMapping = (Fetchable) identifierMapping; + return createFetch( fetchParent, fetchableIdentifierMapping, true ); + } + + private Fetch createFetch(FetchParent fetchParent, Fetchable fetchable, Boolean isKeyFetchable) { final NavigablePath resolvedNavigablePath = fetchParent.resolveNavigablePath( fetchable ); final Map.Entry> sqlSelectionsToTrack = trackedFetchSelectionsForGroup.get( resolvedNavigablePath ); final int sqlSelectionStartIndexForFetch; @@ -7308,8 +7319,7 @@ else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { ); if ( biDirectionalFetch != null ) { - fetches.add( biDirectionalFetch ); - return; + return biDirectionalFetch; } } final Fetch fetch = buildFetch( @@ -7344,8 +7354,8 @@ else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { currentBagRole = fetchable.getNavigableRole().getNavigableName(); } } - fetches.add( fetch ); } + return fetch; } finally { if ( incrementFetchDepth ) { @@ -7374,10 +7384,16 @@ public ImmutableFetchList visitFetches(FetchParent fetchParent) { final int size = referencedMappingContainer.getNumberOfFetchables(); final ImmutableFetchList.Builder fetches = new ImmutableFetchList.Builder( referencedMappingContainer ); for ( int i = 0; i < keySize; i++ ) { - addFetch( fetches, fetchParent, referencedMappingContainer.getKeyFetchable( i ), true ); + final Fetch fetch = createFetch( fetchParent, referencedMappingContainer.getKeyFetchable( i ), true ); + if ( fetch != null ) { + fetches.add( fetch ); + } } for ( int i = 0; i < size; i++ ) { - addFetch( fetches, fetchParent, referencedMappingContainer.getFetchable( i ), false ); + final Fetch fetch = createFetch( fetchParent, referencedMappingContainer.getFetchable( i ), false ); + if ( fetch != null ) { + fetches.add( fetch ); + } } return fetches.build(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java index c1836b0227fd..706cae6d5736 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/domain/AbstractSqmAttributeJoin.java @@ -49,7 +49,7 @@ public AbstractSqmAttributeJoin( NodeBuilder nodeBuilder) { this( lhs, - SqmCreationHelper.buildSubNavigablePath( lhs, joinedNavigable.getName(), alias ), + joinedNavigable.createNavigablePath( lhs, alias ), joinedNavigable, alias == SqmCreationHelper.IMPLICIT_ALIAS ? null : alias, joinType, diff --git a/hibernate-core/src/main/java/org/hibernate/spi/EntityIdentifierNavigablePath.java b/hibernate-core/src/main/java/org/hibernate/spi/EntityIdentifierNavigablePath.java index 4b4be3681187..17cecb363247 100644 --- a/hibernate-core/src/main/java/org/hibernate/spi/EntityIdentifierNavigablePath.java +++ b/hibernate-core/src/main/java/org/hibernate/spi/EntityIdentifierNavigablePath.java @@ -24,6 +24,11 @@ public EntityIdentifierNavigablePath(NavigablePath parent, String identifierAttr this.identifierAttributeName = identifierAttributeName; } + public EntityIdentifierNavigablePath(NavigablePath parent, String alias, String identifierAttributeName) { + super( parent, EntityIdentifierMapping.ROLE_LOCAL_NAME, alias ); + this.identifierAttributeName = identifierAttributeName; + } + public String getIdentifierAttributeName() { return identifierAttributeName; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParent.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParent.java index 7947c2d9fd41..6e157d83aba7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParent.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/FetchParent.java @@ -10,6 +10,7 @@ import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; @@ -36,7 +37,7 @@ public interface FetchParent extends DomainResultGraphNode { default NavigablePath resolveNavigablePath(Fetchable fetchable) { final String fetchableName = fetchable.getFetchableName(); - if ( NavigablePath.IDENTIFIER_MAPPER_PROPERTY.equals( fetchableName ) ) { + if ( NavigablePath.IDENTIFIER_MAPPER_PROPERTY.equals( fetchableName ) || fetchable instanceof EntityIdentifierMapping ) { return new EntityIdentifierNavigablePath( getNavigablePath(), fetchableName ); } else { @@ -53,8 +54,7 @@ else if ( referencedMappingContainer instanceof EntityMappingType ) { else { fetchParentType = fetchableEntityType; } - if ( fetchParentType != fetchableEntityType ) { - // todo (6.0): if the fetchParentType is a subtype of fetchableEntityType this shouldn't be necessary + if ( fetchParentType != null && !fetchParentType.isTypeOrSuperType( fetchableEntityType ) ) { return getNavigablePath().treatAs( fetchableEntityType.getEntityName() ) .append( fetchableName ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultImpl.java index 8f07ff307b6a..79d9c6de3ebe 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultImpl.java @@ -47,7 +47,7 @@ public NavigablePath resolveNavigablePath(Fetchable fetchable) { for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) { final NavigablePath navigablePath = tableGroupJoin.getNavigablePath(); if ( tableGroupJoin.getJoinedGroup().isFetched() - && fetchable.getFetchableName().equals( navigablePath.getLocalName() ) + && fetchable.getNavigableRole().getLocalName().equals( navigablePath.getLocalName() ) && tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) { return navigablePath; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphTraversalStateImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphTraversalStateImpl.java index 875bffd9172b..c9992da180fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphTraversalStateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphTraversalStateImpl.java @@ -17,6 +17,7 @@ import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.graph.spi.SubGraphImplementor; import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.sql.results.graph.EntityGraphTraversalState; @@ -46,6 +47,9 @@ public void backtrack(TraversalResult previousContext) { @Override public TraversalResult traverse(FetchParent fetchParent, Fetchable fetchable, boolean exploreKeySubgraph) { assert !(fetchable instanceof CollectionPart); + if ( fetchable instanceof NonAggregatedIdentifierMapping ) { + return new TraversalResult( currentGraphContext, FetchTiming.IMMEDIATE, true ); + } final GraphImplementor previousContextRoot = currentGraphContext; AttributeNodeImplementor attributeNode = null; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/cdi/FetchEmbeddedIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/cdi/FetchEmbeddedIdTest.java new file mode 100644 index 000000000000..824e4413fef8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/cdi/FetchEmbeddedIdTest.java @@ -0,0 +1,271 @@ +package org.hibernate.orm.test.jpa.cdi; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; +import org.hibernate.testing.orm.junit.Jpa; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Fetch; +import jakarta.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Jpa( + annotatedClasses = { + FetchEmbeddedIdTest.User.class, + FetchEmbeddedIdTest.GroupType.class, + FetchEmbeddedIdTest.Group.class, + FetchEmbeddedIdTest.UserGroup.class + } +) +@TestForIssue( jiraKey = "HHH-15875") +public class FetchEmbeddedIdTest { + + @BeforeAll + public void setUp(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + User user = new User( 1l, "user name" ); + + GroupType groupType = new GroupType( 1l, "group type" ); + Group group = new Group( 1l, "user group", groupType ); + + UserGroupId userGroupId = new UserGroupId( user, group ); + + UserGroup userGroup = new UserGroup( userGroupId, "value" ); + + entityManager.persist( groupType ); + entityManager.persist( group ); + entityManager.persist( user ); + entityManager.persist( userGroup ); + } + ); + + } + + @Test + public void testCriteriaFetch(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); + CriteriaQuery query = criteriaBuilder.createQuery( UserGroup.class ); + + Root root = query.from( UserGroup.class ); + + Fetch userGroupFetch = root.fetch( "userGroupId" ); + userGroupFetch.fetch( "user" ); + userGroupFetch.fetch( "group" ).fetch( "groupType" ); + + List results = entityManager.createQuery( query ).getResultList(); + assertThat( results ).hasSize( 1 ); + + UserGroup userGroup = results.get( 0 ); + UserGroupId userGroupId = userGroup.getUserGroupId(); + Group group = userGroupId.getGroup(); + assertTrue( Hibernate.isInitialized( group ) ); + String name = group.getName(); + assertThat( name ).isEqualTo( "user group" ); + + User user = userGroupId.getUser(); + assertTrue( Hibernate.isInitialized( user ) ); + } + ); + } + + @Test + public void testHqlFetch(EntityManagerFactoryScope scope) { + scope.inTransaction( + entityManager -> { + + List results = entityManager.createQuery( "select ug from UserGroup ug join fetch ug.userGroupId ugi join fetch ugi.group join fetch ugi.user" ).getResultList(); + assertThat( results ).hasSize( 1 ); + + UserGroup userGroup = results.get( 0 ); + UserGroupId userGroupId = userGroup.getUserGroupId(); + Group group = userGroupId.getGroup(); + assertTrue( Hibernate.isInitialized( group ) ); + String name = group.getName(); + assertThat( name ).isEqualTo( "user group" ); + + User user = userGroupId.getUser(); + assertTrue( Hibernate.isInitialized( user ) ); + } + ); + } + + @Entity(name = "UserGroup") + public static class UserGroup { + + @EmbeddedId + private UserGroupId userGroupId; + + private String joinedPropertyValue; + + public UserGroup() { + } + + public UserGroup(UserGroupId userGroupId, String joinedPropertyValue) { + this.userGroupId = userGroupId; + this.joinedPropertyValue = joinedPropertyValue; + } + + public UserGroupId getUserGroupId() { + return userGroupId; + } + + public String getJoinedPropertyValue() { + return joinedPropertyValue; + } + } + + @Embeddable + public static class UserGroupId implements Serializable { + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + private Group group; + + public UserGroupId() { + } + + public UserGroupId(User user, Group group) { + this.user = user; + this.group = group; + } + + public User getUser() { + return user; + } + + public Group getGroup() { + return group; + } + + @Override + public boolean equals(Object object) { + if ( this == object ) { + return true; + } + if ( !( object instanceof UserGroupId ) ) { + return false; + } + + UserGroupId that = (UserGroupId) object; + + return Objects.equals( user.getId(), that.user.getId() ) && Objects.equals( + group.getId(), + that.group.getId() + ); + } + + @Override + public int hashCode() { + return Objects.hash( user.getId(), group.getId() ); + } + } + + @Entity(name = "User") + @Table(name = "test_user") + public static class User { + + @Id + private Long id; + + private String name; + + public User() { + } + + public User(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } + + @Entity(name = "GROUP") + @Table(name = "test_group") + public static class Group { + + @Id + private Long id; + + private String name; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + private GroupType groupType; + + public Group() { + } + + public Group(Long id, String name, GroupType groupType) { + this.id = id; + this.name = name; + this.groupType = groupType; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public GroupType getGroupType() { + return groupType; + } + } + + @Entity(name = "GroupType") + public static class GroupType { + + @Id + private Long id; + + private String name; + + public GroupType() { + + } + + public GroupType(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } +} \ No newline at end of file From f33e89133e8359dfc1b78c13245df2952d6182ee Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Wed, 8 Feb 2023 15:51:27 +0100 Subject: [PATCH 0042/1497] HHH-16151 - Fix potential NullPointerException in SqmTreePrinter for the like excape character Signed-off-by: Jan Schatteman --- .../java/org/hibernate/query/sqm/internal/SqmTreePrinter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java index 15d85c6d7354..d6eefcc88248 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmTreePrinter.java @@ -958,7 +958,9 @@ public Object visitLikePredicate(SqmLikePredicate predicate) { () -> { predicate.getPattern().accept( this ); predicate.getMatchExpression().accept( this ); - predicate.getEscapeCharacter().accept( this ); + if ( predicate.getEscapeCharacter() != null ) { + predicate.getEscapeCharacter().accept( this ); + } } ); return null; From b9189093599c6e599d05eca0f1db34bd2e36af23 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 8 Feb 2023 15:00:56 +0100 Subject: [PATCH 0043/1497] HHH-16152 Documentation for @PartitionKey --- .../userguide/appendices/Annotations.adoc | 7 ++ .../chapters/domain/DomainModel.adoc | 1 + .../chapters/domain/partitioning.adoc | 41 +++++++ .../identifier/SimplePartitionKeyTest.java | 104 ++++++++++++++++++ 4 files changed, 153 insertions(+) create mode 100644 documentation/src/main/asciidoc/userguide/chapters/domain/partitioning.adoc create mode 100644 documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimplePartitionKeyTest.java diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index 511aebdded2c..5da843760c2f 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -1184,6 +1184,13 @@ The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibern See the <> section for more info. +[[annotations-hibernate-partition-key]] +==== `@PartitionKey` + +The https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/annotations/PartitionKey.html[`@PartitionKey`] annotation is used to identify a field of an entity that holds the partition key of a table. + +See the <> section for more info. + [[annotations-hibernate-persister]] ==== `@Persister` diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/DomainModel.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/DomainModel.adoc index f23009dbdeaf..74702582c2e7 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/DomainModel.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/DomainModel.adoc @@ -32,6 +32,7 @@ include::identifiers.adoc[] include::associations.adoc[] include::collections.adoc[] include::natural_id.adoc[] +include::partitioning.adoc[] include::dynamic_model.adoc[] include::inheritance.adoc[] include::immutability.adoc[] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/partitioning.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/partitioning.adoc new file mode 100644 index 000000000000..d286ef0e59a0 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/partitioning.adoc @@ -0,0 +1,41 @@ +[[partitioning]] +=== Partitioning +:root-project-dir: ../../../../../../.. +:documentation-project-dir: {root-project-dir}/documentation +:example-dir-partition-key: {documentation-project-dir}/src/test/java/org/hibernate/userguide/mapping/identifier +:extrasdir: extras + +In data management, it is sometimes necessary to split data of a table into various (physical) partitions, +based on partition keys and a partitioning scheme. + +Due to the nature of partitioning, it is vital for the database to know the partition key of a row for certain operations, +like SQL update and delete statements. If a database doesn't know the partition of a row that should be updated or deleted, +then it must look for the row in all partitions, leading to poor performance. + +The `@PartitionKey` annotation is a way to tell Hibernate about the column, such that it can include a column restriction as +predicate into SQL update and delete statements for entity state changes. + +[[partition-key-mapping]] +==== Partition Key Mapping + +Partition keys are defined in terms of one or more persistent attributes. + +[[partition-key-simple-basic-attribute-mapping-example]] +.Partition key using single basic attribute +==== +[source,java] +---- +include::{example-dir-partition-key}/SimplePartitionKeyTest.java[tags=partition-key-simple-basic-attribute-mapping-example,indent=0] +---- +==== + +When updating or deleting an entity, Hibernate will include a partition key constraint similar to this + +[[partition-key-simple-basic-attribute-sql-example]] +==== +[source,sql] +---- +update user_tbl set firstname=?,lastname=?,tenantKey=? where id=? and tenantKey=? +delete from user_tbl where id=? and tenantKey=? +---- +==== \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimplePartitionKeyTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimplePartitionKeyTest.java new file mode 100644 index 000000000000..6fc625853766 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/identifier/SimplePartitionKeyTest.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.userguide.mapping.identifier; + +import org.hibernate.annotations.PartitionKey; +import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Christian Beikov + */ +public class SimplePartitionKeyTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + User.class + }; + } + + @Test + public void test() { + doInJPA(this::entityManagerFactory, entityManager -> { + User user = new User(); + user.setId( 1L ); + user.setFirstname( "John" ); + user.setLastname( "Doe" ); + user.setTenantKey( "tenant1" ); + + entityManager.persist( user ); + }); + doInJPA( this::entityManagerFactory, entityManager -> { + User user = entityManager.find( User.class, 1L ); + user.setLastname( "Cash" ); + } ); + doInJPA( this::entityManagerFactory, entityManager -> { + entityManager.remove( entityManager.find( User.class, 1L ) ); + } ); + } + + @Table(name = "user_tbl") + //tag::partition-key-simple-basic-attribute-mapping-example[] + @Entity(name = "User") + public static class User { + + @Id + private Long id; + + private String firstname; + + private String lastname; + + @PartitionKey + private String tenantKey; + + //Getters and setters are omitted for brevity + //end::partition-key-simple-basic-attribute-mapping-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String title) { + this.firstname = title; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String author) { + this.lastname = author; + } + + public String getTenantKey() { + return tenantKey; + } + + public void setTenantKey(String tenantKey) { + this.tenantKey = tenantKey; + } + //tag::partition-key-simple-basic-attribute-mapping-example[] + } + //end::partition-key-simple-basic-attribute-mapping-example[] +} From 989a127b178d3e0b9dc51b30aeb3c8d17dc08ecf Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Thu, 2 Feb 2023 16:15:28 +0100 Subject: [PATCH 0044/1497] HHH-16131 - Added workaround and test for date calculcation errors on Oracle Temporarily excluded TiDB from that test (until they fix https://github.com/pingcap/tidb/issues/41052) Added tidb to the docker_db script Signed-off-by: Jan Schatteman --- docker_db.sh | 29 +++++++++ .../org/hibernate/dialect/OracleDialect.java | 59 +++++++------------ .../orm/test/query/hql/FunctionTests.java | 44 ++++++++++---- 3 files changed, 82 insertions(+), 50 deletions(-) diff --git a/docker_db.sh b/docker_db.sh index e250e4e1d13a..c095b49d940e 100755 --- a/docker_db.sh +++ b/docker_db.sh @@ -769,6 +769,33 @@ EOF } +tidb() { + tidb_5_1 +} + +tidb_5_1() { + $CONTAINER_CLI rm -f tidb || true + $CONTAINER_CLI run --name tidb -p4000:4000 -d docker.io/pingcap/tidb:v5.1.4 + # Give the container some time to start + OUTPUT= + n=0 + until [ "$n" -ge 5 ] + do + OUTPUT=$($CONTAINER_CLI logs tidb 2>&1) + if [[ $OUTPUT == *"server is running"* ]]; then + break; + fi + n=$((n+1)) + echo "Waiting for TiDB to start..." + sleep 3 + done + if [ "$n" -ge 5 ]; then + echo "TiDB failed to start and configure after 15 seconds" + else + echo "TiDB successfully started" + fi +} + if [ -z ${1} ]; then echo "No db name provided" echo "Provide one of:" @@ -804,6 +831,8 @@ if [ -z ${1} ]; then echo -e "\tpostgresql_10" echo -e "\tpostgresql_9_5" echo -e "\tsybase" + echo -e "\ttidb" + echo -e "\ttidb_5_1" else ${1} fi 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 e7500d0f58cb..ee0e97e1a7a6 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -150,6 +150,14 @@ public class OracleDialect extends Dialect { public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw"; + private static final String yqmSelect = + "( SELECT b_.bd + ( LEAST( EXTRACT( DAY FROM b_.od ), EXTRACT( DAY FROM LAST_DAY( b_.bd ) ) ) - 1 )\n" + + "FROM (SELECT a_.od, TRUNC(a_.od, 'MONTH') + NUMTOYMINTERVAL(%1$s, 'MONTH') bd FROM ( SELECT %2$s od FROM dual ) a_) b_ ) "; + + private static final String ADD_YEAR_EXPRESSION = String.format( yqmSelect, "?2*12", "?3" ); + private static final String ADD_QUARTER_EXPRESSION = String.format( yqmSelect, "?2*3", "?3" ); + private static final String ADD_MONTH_EXPRESSION = String.format( yqmSelect, "?2", "?3" ); + private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 11, 2 ); private final LimitHandler limitHandler = supportsFetchClause( FetchClauseType.ROWS_ONLY ) @@ -480,63 +488,36 @@ public String extractPattern(TemporalUnit unit) { @Override public String timestampaddPattern(TemporalUnit unit, TemporalType temporalType, IntervalType intervalType) { + StringBuilder pattern = new StringBuilder(); - pattern.append("(?3+"); switch ( unit ) { case YEAR: + pattern.append( ADD_YEAR_EXPRESSION ); + break; case QUARTER: + pattern.append( ADD_QUARTER_EXPRESSION ); + break; case MONTH: - pattern.append("numtoyminterval"); + pattern.append( ADD_MONTH_EXPRESSION ); break; case WEEK: + pattern.append("(?3+numtodsinterval((?2)*7,'day'))"); + break; case DAY: case HOUR: case MINUTE: case SECOND: - case NANOSECOND: - case NATIVE: - pattern.append("numtodsinterval"); - break; - default: - throw new SemanticException(unit + " is not a legal field"); - } - pattern.append("("); - switch ( unit ) { - case NANOSECOND: - case QUARTER: - case WEEK: - pattern.append("("); - break; - } - pattern.append("?2"); - switch ( unit ) { - case QUARTER: - pattern.append(")*3"); - break; - case WEEK: - pattern.append(")*7"); + pattern.append("(?3+numtodsinterval(?2,'?1'))"); break; case NANOSECOND: - pattern.append(")/1e9"); - break; - } - pattern.append(",'"); - switch ( unit ) { - case QUARTER: - pattern.append("month"); - break; - case WEEK: - pattern.append("day"); + pattern.append("(?3+numtodsinterval((?2)/1e9,'second'))"); break; - case NANOSECOND: case NATIVE: - pattern.append("second"); + pattern.append("(?3+numtodsinterval(?2,'second'))"); break; default: - pattern.append("?1"); + throw new SemanticException(unit + " is not a legal field"); } - pattern.append("')"); - pattern.append(")"); return pattern.toString(); } 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 a4728aaed5d8..69b5308b61ca 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 @@ -10,14 +10,12 @@ import org.hibernate.dialect.CockroachDialect; import org.hibernate.dialect.DB2Dialect; import org.hibernate.dialect.DerbyDialect; - import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MariaDBDialect; import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.TiDBDialect; - import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.domain.StandardDomainModel; import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; @@ -28,10 +26,10 @@ import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.RequiresDialect; 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; @@ -51,6 +49,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.isOneOf; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -58,7 +57,6 @@ /** * @author Gavin King */ -@ServiceRegistry @DomainModel( standardModels = StandardDomainModel.GAMBIT ) @SessionFactory public class FunctionTests { @@ -1314,20 +1312,44 @@ public void testDurationLiterals(SessionFactoryScope scope) { } @Test + @SkipForDialect( dialectClass = TiDBDialect.class, reason = "Bug in the TiDB timestampadd function (https://github.com/pingcap/tidb/issues/41052)") public void testDurationArithmetic(SessionFactoryScope scope) { scope.inTransaction( session -> { assertEquals( LocalDate.now().minus(2, ChronoUnit.DAYS), - session.createQuery("select local date - 2 day") + session.createQuery("select local date - 2 day", LocalDate.class) .getSingleResult() ); assertEquals( LocalDate.now().plus(1, ChronoUnit.MONTHS), - session.createQuery("select local date + 1 month") - .getSingleResult() ); - session.createQuery("select e.theTimestamp - 21 second from EntityOfBasics e") + session.createQuery("select local date + 1 month", LocalDate.class) + .getSingleResult() ); + assertEquals( LocalDate.now().plus(1, ChronoUnit.YEARS), + session.createQuery("select local date + 1 year", LocalDate.class) + .getSingleResult() ); + assertEquals( LocalDate.now().plus(3, ChronoUnit.MONTHS), + session.createQuery("select local date + 1 quarter", LocalDate.class) + .getSingleResult() ); + // Some explicit 'special' cases: + assertEquals( LocalDate.of(2024, 02, 29), + session.createQuery("select {2024-01-31} + 1 month", LocalDate.class) + .getSingleResult() ); + assertEquals( LocalDate.of(2025, 02, 28), + session.createQuery("select {2024-02-29} + 1 year", LocalDate.class) + .getSingleResult() ); + assertEquals( LocalDate.of(2028, 02, 29), + session.createQuery("select {2024-02-29} + 4 year", LocalDate.class) + .getSingleResult() ); + assertEquals( LocalDate.of(2025, 03, 29), + session.createQuery("select {2024-02-29} + 13 month", LocalDate.class) + .getSingleResult() ); + assertEquals( LocalDate.of(2024, 02, 29), + session.createQuery("select {2023-11-30} + 1 quarter", LocalDate.class) + .getSingleResult() ); + + session.createQuery("select e.theTimestamp - 21 second from EntityOfBasics e", java.util.Date.class) .getSingleResult(); - session.createQuery("select e.theTimestamp + 2 day from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 2 day from EntityOfBasics e", java.util.Date.class) .getSingleResult(); - session.createQuery("select e.theTimestamp - 21 second + 2 day from EntityOfBasics e") + session.createQuery("select e.theTimestamp - 21 second + 2 day from EntityOfBasics e", java.util.Date.class) .getSingleResult(); //TODO: FIX!! // session.createQuery("select e.theTimestamp + 2 * e.theDuration from EntityOfBasics e") @@ -1786,4 +1808,4 @@ public void testIn(SessionFactoryScope scope) { ); } -} \ No newline at end of file +} From 7250449aaa58b4421cc346f9505c0dd308b982f2 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Tue, 7 Feb 2023 20:54:08 +0100 Subject: [PATCH 0045/1497] Remove calls to deprecated createQuery method from FunctionTests Signed-off-by: Jan Schatteman --- .../orm/test/query/hql/FunctionTests.java | 919 +++++++++--------- 1 file changed, 446 insertions(+), 473 deletions(-) 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 69b5308b61ca..173aa1e7aa88 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 @@ -33,13 +33,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.sql.Date; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Date; import java.sql.Time; import java.sql.Timestamp; import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; @@ -50,8 +54,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.isOneOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.assertThrows; /** @@ -129,7 +133,7 @@ public void testLowerFunctionsOrdinalEnumsShouldFail(SessionFactoryScope scope) scope.inTransaction( session -> assertThrows( IllegalArgumentException.class, () -> - session.createQuery( "select lower(e.ordinalGender) from EntityOfBasics e" ) + session.createQuery( "select lower(e.ordinalGender) from EntityOfBasics e", String.class ) .list() ) ); @@ -139,11 +143,11 @@ public void testLowerFunctionsOrdinalEnumsShouldFail(SessionFactoryScope scope) public void testIdVersionFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select id(w) from VersionedEntity w") + session.createQuery("select id(w) from VersionedEntity w", Integer.class) .list(); - session.createQuery("select version(w) from VersionedEntity w") + session.createQuery("select version(w) from VersionedEntity w", Integer.class) .list(); - session.createQuery("select naturalid(w) from VersionedEntity w") + session.createQuery("select naturalid(w) from VersionedEntity w", String.class) .list(); } ); @@ -188,28 +192,28 @@ public void testImplicitCollectionJoinInSelect(SessionFactoryScope scope) { public void testImplicitCollectionJoinInWhere(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("from EntityOfLists eol where index(eol.listOfNumbers)=0") + session.createQuery("from EntityOfLists eol where index(eol.listOfNumbers)=0", EntityOfLists.class) .getResultList(); - session.createQuery("from EntityOfLists eol where element(eol.listOfNumbers)=1.0") + session.createQuery("from EntityOfLists eol where element(eol.listOfNumbers)=1.0", EntityOfLists.class) .getResultList(); - session.createQuery("from EntityOfMaps eom where key(eom.numberByNumber)=1") + session.createQuery("from EntityOfMaps eom where key(eom.numberByNumber)=1", EntityOfMaps.class) .getResultList(); - session.createQuery("from EntityOfMaps eom where value(eom.numberByNumber)=1.0") + session.createQuery("from EntityOfMaps eom where value(eom.numberByNumber)=1.0", EntityOfMaps.class) .getResultList(); - session.createQuery("from EntityOfMaps eom where key(eom.basicByBasic)='hello'") + session.createQuery("from EntityOfMaps eom where key(eom.basicByBasic)='hello'", EntityOfMaps.class) .getResultList(); - session.createQuery("from EntityOfMaps eom where value(eom.basicByBasic)='world'") + session.createQuery("from EntityOfMaps eom where value(eom.basicByBasic)='world'", EntityOfMaps.class) .getResultList(); - session.createQuery("from EntityOfLists eol join eol.listOfOneToMany se where element(se).someLong=5") + session.createQuery("from EntityOfLists eol join eol.listOfOneToMany se where element(se).someLong=5", EntityOfLists.class) .getSingleResult(); - session.createQuery("from EntityOfLists eol where element(eol.listOfOneToMany).someLong=5") + session.createQuery("from EntityOfLists eol where element(eol.listOfOneToMany).someLong=5", EntityOfLists.class) .getSingleResult(); - session.createQuery("from EntityOfLists eol join eol.listOfManyToMany se where element(se).someLong=10") + session.createQuery("from EntityOfLists eol join eol.listOfManyToMany se where element(se).someLong=10", EntityOfLists.class) .getSingleResult(); - session.createQuery("from EntityOfLists eol where element(eol.listOfManyToMany).someLong=10") + session.createQuery("from EntityOfLists eol where element(eol.listOfManyToMany).someLong=10", EntityOfLists.class) .getSingleResult(); } ); @@ -219,34 +223,34 @@ public void testImplicitCollectionJoinInWhere(SessionFactoryScope scope) { public void testImplicitCollectionJoinInSelectAggregate(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select max(index(eol.listOfNumbers)) from EntityOfLists eol group by eol") + assertThat( session.createQuery("select max(index(eol.listOfNumbers)) from EntityOfLists eol group by eol", Integer.class) .getSingleResult(), is(1) ); - assertThat( session.createQuery("select max(element(eol.listOfNumbers)) from EntityOfLists eol group by eol") + assertThat( session.createQuery("select max(element(eol.listOfNumbers)) from EntityOfLists eol group by eol", Double.class) .getSingleResult(), is(2.0) ); - assertThat( session.createQuery("select sum(index(eol.listOfNumbers)) from EntityOfLists eol group by eol") + assertThat( session.createQuery("select sum(index(eol.listOfNumbers)) from EntityOfLists eol group by eol", Long.class) .getSingleResult(), is(1L) ); - assertThat( session.createQuery("select sum(element(eol.listOfNumbers)) from EntityOfLists eol group by eol") + assertThat( session.createQuery("select sum(element(eol.listOfNumbers)) from EntityOfLists eol group by eol", Double.class) .getSingleResult(), is(3.0) ); - assertThat( session.createQuery("select avg(index(eol.listOfNumbers)) from EntityOfLists eol group by eol") + assertThat( session.createQuery("select avg(index(eol.listOfNumbers)) from EntityOfLists eol group by eol", Double.class) .getSingleResult(), is(0.5) ); - assertThat( session.createQuery("select avg(element(eol.listOfNumbers)) from EntityOfLists eol group by eol") + assertThat( session.createQuery("select avg(element(eol.listOfNumbers)) from EntityOfLists eol group by eol", Double.class) .getSingleResult(), is(1.5) ); - assertThat( session.createQuery("select max(key(eom.numberByNumber)) from EntityOfMaps eom group by eom") + assertThat( session.createQuery("select max(key(eom.numberByNumber)) from EntityOfMaps eom group by eom", Integer.class) .getSingleResult(), is(1) ); - assertThat( session.createQuery("select max(value(eom.numberByNumber)) from EntityOfMaps eom group by eom") + assertThat( session.createQuery("select max(value(eom.numberByNumber)) from EntityOfMaps eom group by eom", Double.class) .getSingleResult(), is(1.0) ); - assertThat( session.createQuery("select sum(key(eom.numberByNumber)) from EntityOfMaps eom group by eom") + assertThat( session.createQuery("select sum(key(eom.numberByNumber)) from EntityOfMaps eom group by eom", Long.class) .getSingleResult(), is(1L) ); - assertThat( session.createQuery("select sum(value(eom.numberByNumber)) from EntityOfMaps eom group by eom") + assertThat( session.createQuery("select sum(value(eom.numberByNumber)) from EntityOfMaps eom group by eom", Double.class) .getSingleResult(), is(1.0) ); - assertThat( session.createQuery("select avg(key(eom.numberByNumber)) from EntityOfMaps eom group by eom") + assertThat( session.createQuery("select avg(key(eom.numberByNumber)) from EntityOfMaps eom group by eom", Double.class) .getSingleResult(), is(1.0) ); - assertThat( session.createQuery("select avg(value(eom.numberByNumber)) from EntityOfMaps eom group by eom") + assertThat( session.createQuery("select avg(value(eom.numberByNumber)) from EntityOfMaps eom group by eom", Double.class) .getSingleResult(), is(1.0) ); } ); @@ -256,34 +260,34 @@ public void testImplicitCollectionJoinInSelectAggregate(SessionFactoryScope scop public void testAggregateIndicesElementsWithPath(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select max(indices(eol.listOfNumbers)) from EntityOfLists eol") + assertThat( session.createQuery("select max(indices(eol.listOfNumbers)) from EntityOfLists eol", Integer.class) .getSingleResult(), is(1) ); - assertThat( session.createQuery("select max(elements(eol.listOfNumbers)) from EntityOfLists eol") + assertThat( session.createQuery("select max(elements(eol.listOfNumbers)) from EntityOfLists eol", Double.class) .getSingleResult(), is(2.0) ); - assertThat( session.createQuery("select sum(indices(eol.listOfNumbers)) from EntityOfLists eol") + assertThat( session.createQuery("select sum(indices(eol.listOfNumbers)) from EntityOfLists eol", Long.class) .getSingleResult(), is(1L) ); - assertThat( session.createQuery("select sum(elements(eol.listOfNumbers)) from EntityOfLists eol") + assertThat( session.createQuery("select sum(elements(eol.listOfNumbers)) from EntityOfLists eol", Double.class) .getSingleResult(), is(3.0) ); - assertThat( session.createQuery("select avg(indices(eol.listOfNumbers)) from EntityOfLists eol") + assertThat( session.createQuery("select avg(indices(eol.listOfNumbers)) from EntityOfLists eol", Double.class) .getSingleResult(), is(0.5) ); - assertThat( session.createQuery("select avg(elements(eol.listOfNumbers)) from EntityOfLists eol") + assertThat( session.createQuery("select avg(elements(eol.listOfNumbers)) from EntityOfLists eol", Double.class) .getSingleResult(), is(1.5) ); - assertThat( session.createQuery("select max(indices(eom.numberByNumber)) from EntityOfMaps eom") + assertThat( session.createQuery("select max(indices(eom.numberByNumber)) from EntityOfMaps eom", Integer.class) .getSingleResult(), is(1) ); - assertThat( session.createQuery("select max(elements(eom.numberByNumber)) from EntityOfMaps eom") + assertThat( session.createQuery("select max(elements(eom.numberByNumber)) from EntityOfMaps eom", Double.class) .getSingleResult(), is(1.0) ); - assertThat( session.createQuery("select sum(indices(eom.numberByNumber)) from EntityOfMaps eom") + assertThat( session.createQuery("select sum(indices(eom.numberByNumber)) from EntityOfMaps eom", Long.class) .getSingleResult(), is(1L) ); - assertThat( session.createQuery("select sum(elements(eom.numberByNumber)) from EntityOfMaps eom") + assertThat( session.createQuery("select sum(elements(eom.numberByNumber)) from EntityOfMaps eom", Double.class) .getSingleResult(), is(1.0) ); - assertThat( session.createQuery("select avg(indices(eom.numberByNumber)) from EntityOfMaps eom") + assertThat( session.createQuery("select avg(indices(eom.numberByNumber)) from EntityOfMaps eom", Double.class) .getSingleResult(), is(1.0) ); - assertThat( session.createQuery("select avg(elements(eom.numberByNumber)) from EntityOfMaps eom") + assertThat( session.createQuery("select avg(elements(eom.numberByNumber)) from EntityOfMaps eom", Double.class) .getSingleResult(), is(1.0) ); } ); @@ -293,34 +297,34 @@ public void testAggregateIndicesElementsWithPath(SessionFactoryScope scope) { public void testAggregateIndexElementKeyValueWithAlias(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select max(index(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol") + assertThat( session.createQuery("select max(index(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol", Integer.class) .getSingleResult(), is(1) ); - assertThat( session.createQuery("select max(element(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol") + assertThat( session.createQuery("select max(element(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol", Double.class) .getSingleResult(), is(2.0) ); - assertThat( session.createQuery("select sum(index(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol") + assertThat( session.createQuery("select sum(index(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol", Long.class) .getSingleResult(), is(1L) ); - assertThat( session.createQuery("select sum(element(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol") + assertThat( session.createQuery("select sum(element(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol", Double.class) .getSingleResult(), is(3.0) ); - assertThat( session.createQuery("select avg(index(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol") + assertThat( session.createQuery("select avg(index(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol", Double.class) .getSingleResult(), is(0.5) ); - assertThat( session.createQuery("select avg(element(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol") + assertThat( session.createQuery("select avg(element(l)) from EntityOfLists eol join eol.listOfNumbers l group by eol", Double.class) .getSingleResult(), is(1.5) ); - assertThat( session.createQuery("select max(key(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom") + assertThat( session.createQuery("select max(key(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom", Integer.class) .getSingleResult(), is(1) ); - assertThat( session.createQuery("select max(value(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom") + assertThat( session.createQuery("select max(value(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom", Double.class) .getSingleResult(), is(1.0) ); - assertThat( session.createQuery("select sum(key(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom") + assertThat( session.createQuery("select sum(key(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom", Long.class) .getSingleResult(), is(1L) ); - assertThat( session.createQuery("select sum(value(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom") + assertThat( session.createQuery("select sum(value(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom", Double.class) .getSingleResult(), is(1.0) ); - assertThat( session.createQuery("select avg(key(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom") + assertThat( session.createQuery("select avg(key(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom", Double.class) .getSingleResult(), is(1.0) ); - assertThat( session.createQuery("select avg(value(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom") + assertThat( session.createQuery("select avg(value(m)) from EntityOfMaps eom join eom.numberByNumber m group by eom", Double.class) .getSingleResult(), is(1.0) ); } ); @@ -330,14 +334,14 @@ public void testAggregateIndexElementKeyValueWithAlias(SessionFactoryScope scope public void testMaxindexMaxelement(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select maxindex(eol.listOfBasics) from EntityOfLists eol") + assertThat( session.createQuery("select maxindex(eol.listOfBasics) from EntityOfLists eol", Integer.class) .getSingleResult(), is(0) ); - assertThat( session.createQuery("select maxelement(eol.listOfBasics) from EntityOfLists eol") + assertThat( session.createQuery("select maxelement(eol.listOfBasics) from EntityOfLists eol", String.class) .getSingleResult(), is("hello") ); - assertThat( session.createQuery("select maxindex(eom.basicByBasic) from EntityOfMaps eom") + assertThat( session.createQuery("select maxindex(eom.basicByBasic) from EntityOfMaps eom", String.class) .getSingleResult(), is("hello") ); - assertThat( session.createQuery("select maxelement(eom.basicByBasic) from EntityOfMaps eom") + assertThat( session.createQuery("select maxelement(eom.basicByBasic) from EntityOfMaps eom", String.class) .getSingleResult(), is("world") ); } ); @@ -347,19 +351,19 @@ public void testMaxindexMaxelement(SessionFactoryScope scope) { public void testKeyIndexValueEntry(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select index(l) from EntityOfLists eol join eol.listOfBasics l") + assertThat( session.createQuery("select index(l) from EntityOfLists eol join eol.listOfBasics l", Integer.class) .getSingleResult(), is(0) ); - assertThat( session.createQuery("select value(l) from EntityOfLists eol join eol.listOfBasics l") + assertThat( session.createQuery("select value(l) from EntityOfLists eol join eol.listOfBasics l", String.class) .getSingleResult(), is("hello") ); - assertThat( session.createQuery("select key(m) from EntityOfMaps eom join eom.basicByBasic m") + assertThat( session.createQuery("select key(m) from EntityOfMaps eom join eom.basicByBasic m", String.class) .getSingleResult(), is("hello") ); - assertThat( session.createQuery("select index(m) from EntityOfMaps eom join eom.basicByBasic m") + assertThat( session.createQuery("select index(m) from EntityOfMaps eom join eom.basicByBasic m", String.class) .getSingleResult(), is("hello") ); - assertThat( session.createQuery("select value(m) from EntityOfMaps eom join eom.basicByBasic m") + assertThat( session.createQuery("select value(m) from EntityOfMaps eom join eom.basicByBasic m", String.class) .getSingleResult(), is("world") ); - assertThat( session.createQuery("select entry(m) from EntityOfMaps eom join eom.basicByBasic m") + assertThat( session.createQuery("select entry(m) from EntityOfMaps eom join eom.basicByBasic m", Map.Entry.class) .getSingleResult(), is( Map.entry("hello", "world") ) ); } ); @@ -370,8 +374,8 @@ public void testKeyIndexValueEntry(SessionFactoryScope scope) { public void testAsciiChrFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select chr(65)").getSingleResult(), is( 'A' ) ); - assertThat( session.createQuery("select ascii('A')").getSingleResult(), anyOf( is( 65 ), is( (short) 65 ) ) ); + assertThat( session.createQuery("select chr(65)", Character.class).getSingleResult(), is( 'A' ) ); + assertThat( session.createQuery("select ascii('A')", Integer.class).getSingleResult(), anyOf( is( 65 ), is( (short) 65 ) ) ); } ); } @@ -380,12 +384,12 @@ public void testAsciiChrFunctions(SessionFactoryScope scope) { public void testConcatFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select concat('foo', e.theString, 'bar') from EntityOfBasics e") + session.createQuery("select concat('foo', e.theString, 'bar') from EntityOfBasics e", String.class) .list(); - session.createQuery("select 'foo' || e.theString || 'bar' from EntityOfBasics e") + session.createQuery("select 'foo' || e.theString || 'bar' from EntityOfBasics e", String.class) .list(); - assertThat( session.createQuery("select concat('hello',' ','world')").getSingleResult(), is("hello world") ); - assertThat( session.createQuery("select 'hello'||' '||'world'").getSingleResult(), is("hello world") ); + assertThat( session.createQuery("select concat('hello',' ','world')", String.class).getSingleResult(), is("hello world") ); + assertThat( session.createQuery("select 'hello'||' '||'world'", String.class).getSingleResult(), is("hello world") ); } ); } @@ -394,9 +398,9 @@ public void testConcatFunction(SessionFactoryScope scope) { public void testConcatFunctionParameters(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select cast(:hello as String)||cast(:world as String)").setParameter("hello","hello").setParameter("world","world").getSingleResult(), is("helloworld") ); - assertThat( session.createQuery("select cast(?1 as String)||cast(?2 as String)").setParameter(1,"hello").setParameter(2,"world").getSingleResult(), is("helloworld") ); - assertThat( session.createQuery("select cast(?1 as String)||cast(?1 as String)").setParameter(1,"hello").getSingleResult(), is("hellohello") ); + assertThat( session.createQuery("select cast(:hello as String)||cast(:world as String)", String.class).setParameter("hello","hello").setParameter("world","world").getSingleResult(), is("helloworld") ); + assertThat( session.createQuery("select cast(?1 as String)||cast(?2 as String)", String.class).setParameter(1,"hello").setParameter(2,"world").getSingleResult(), is("helloworld") ); + assertThat( session.createQuery("select cast(?1 as String)||cast(?1 as String)", String.class).setParameter(1,"hello").getSingleResult(), is("hellohello") ); } ); } @@ -405,12 +409,12 @@ public void testConcatFunctionParameters(SessionFactoryScope scope) { public void testCoalesceFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select coalesce(nullif('',''), e.gender, e.convertedGender) from EntityOfBasics e") + session.createQuery("select coalesce(nullif('',''), e.gender, e.convertedGender) from EntityOfBasics e", EntityOfBasics.Gender.class) .list(); - session.createQuery("select ifnull(e.gender, e.convertedGender) from EntityOfBasics e") + session.createQuery("select ifnull(e.gender, e.convertedGender) from EntityOfBasics e", EntityOfBasics.Gender.class) .list(); - assertThat( session.createQuery("select coalesce(nullif('',''), nullif('bye','bye'), 'hello', 'oops')").getSingleResult(), is("hello") ); - assertThat( session.createQuery("select ifnull(nullif('bye','bye'), 'hello')").getSingleResult(), is("hello") ); + assertThat( session.createQuery("select coalesce(nullif('',''), nullif('bye','bye'), 'hello', 'oops')", String.class).getSingleResult(), is("hello") ); + assertThat( session.createQuery("select ifnull(nullif('bye','bye'), 'hello')", String.class).getSingleResult(), is("hello") ); } ); } @@ -419,10 +423,10 @@ public void testCoalesceFunction(SessionFactoryScope scope) { public void testNullifFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select nullif(e.theString, '') from EntityOfBasics e") + session.createQuery("select nullif(e.theString, '') from EntityOfBasics e", String.class) .list(); - assertThat( session.createQuery("select nullif('foo', 'foo')").getSingleResult(), nullValue() ); - assertThat( session.createQuery("select nullif('foo', 'bar')").getSingleResult(), is("foo") ); + assertThat( session.createQuery("select nullif('foo', 'foo')", String.class).getSingleResult(), nullValue() ); + assertThat( session.createQuery("select nullif('foo', 'bar')", String.class).getSingleResult(), is("foo") ); } ); } @@ -431,9 +435,9 @@ public void testNullifFunction(SessionFactoryScope scope) { public void testTrigFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select sin(e.theDouble), cos(e.theDouble), tan(e.theDouble), asin(e.theDouble), acos(e.theDouble), atan(e.theDouble) from EntityOfBasics e") + session.createQuery("select sin(e.theDouble), cos(e.theDouble), tan(e.theDouble), asin(e.theDouble), acos(e.theDouble), atan(e.theDouble) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select atan2(sin(e.theDouble), cos(e.theDouble)) from EntityOfBasics e") + session.createQuery("select atan2(sin(e.theDouble), cos(e.theDouble)) from EntityOfBasics e", Object[].class) .list(); } ); @@ -443,26 +447,26 @@ public void testTrigFunctions(SessionFactoryScope scope) { public void testMathFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select +e.theInt, -e.theInt from EntityOfBasics e") + session.createQuery("select +e.theInt, -e.theInt from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select abs(e.theInt), sign(e.theInt), mod(e.theInt, 2) from EntityOfBasics e") + session.createQuery("select abs(e.theInt), sign(e.theInt), mod(e.theInt, 2) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select e.theInt % 2 from EntityOfBasics e") + session.createQuery("select e.theInt % 2 from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select abs(e.theDouble), sign(e.theDouble), sqrt(e.theDouble) from EntityOfBasics e") + session.createQuery("select abs(e.theDouble), sign(e.theDouble), sqrt(e.theDouble) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select exp(e.theDouble), ln(e.theDouble + 1), log10(e.theDouble + 2) from EntityOfBasics e") + session.createQuery("select exp(e.theDouble), ln(e.theDouble + 1), log10(e.theDouble + 2) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select power(e.theDouble + 1, 2.5) from EntityOfBasics e") + session.createQuery("select power(e.theDouble + 1, 2.5) from EntityOfBasics e", Double.class) .list(); - session.createQuery("select ceiling(e.theDouble), floor(e.theDouble) from EntityOfBasics e") + session.createQuery("select ceiling(e.theDouble), floor(e.theDouble) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select round(e.theDouble, 2) from EntityOfBasics e") + session.createQuery("select round(e.theDouble, 2) from EntityOfBasics e", Double.class) .list(); - session.createQuery("select round(cast(e.theDouble as BigDecimal), 3) from EntityOfBasics e") + session.createQuery("select round(cast(e.theDouble as BigDecimal), 3) from EntityOfBasics e", BigDecimal.class) .list(); - assertThat( session.createQuery("select abs(-2)").getSingleResult(), is(2) ); - assertThat( session.createQuery("select sign(-2)").getSingleResult(), is(-1) ); + assertThat( session.createQuery("select abs(-2)", Integer.class).getSingleResult(), is(2) ); + assertThat( session.createQuery("select sign(-2)", Integer.class).getSingleResult(), is(-1) ); assertThat( session.createQuery("select power(3.0,2.0)", Double.class).getSingleResult(), // The LN/EXP emulation can cause some precision loss @@ -470,10 +474,10 @@ public void testMathFunctions(SessionFactoryScope scope) { // Fetching the result as float would "hide" the error as that would do some rounding Matchers.closeTo( 9.0d, ERROR ) ); - assertThat( session.createQuery("select round(32.12345,2)").getSingleResult(), is(32.12f) ); - assertThat( session.createQuery("select mod(3,2)").getSingleResult(), is(1) ); - assertThat( session.createQuery("select 3%2").getSingleResult(), is(1) ); - assertThat( session.createQuery("select sqrt(9.0)").getSingleResult(), is(3.0d) ); + assertThat( session.createQuery("select round(32.12345,2)", Float.class).getSingleResult(), is(32.12f) ); + assertThat( session.createQuery("select mod(3,2)", Integer.class).getSingleResult(), is(1) ); + assertThat( session.createQuery("select 3%2", Integer.class).getSingleResult(), is(1) ); + assertThat( session.createQuery("select sqrt(9.0)", Double.class).getSingleResult(), is(3.0d) ); } ); } @@ -482,27 +486,27 @@ public void testMathFunctions(SessionFactoryScope scope) { public void testRoundTruncFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select trunc(32.92345f)").getSingleResult(), is(32f) ); - assertThat( session.createQuery("select trunc(32.92345f,3)").getSingleResult(), is(32.923f) ); - assertThat( session.createQuery("select trunc(-32.92345f)").getSingleResult(), is(-32f) ); - assertThat( session.createQuery("select trunc(-32.92345f,3)").getSingleResult(), is(-32.923f) ); - assertThat( session.createQuery("select truncate(32.92345f)").getSingleResult(), is(32f) ); - assertThat( session.createQuery("select truncate(32.92345f,3)").getSingleResult(), is(32.923f) ); - assertThat( session.createQuery("select round(32.92345f)").getSingleResult(), is(33f) ); - assertThat( session.createQuery("select round(32.92345f,1)").getSingleResult(), is(32.9f) ); - assertThat( session.createQuery("select round(32.92345f,3)").getSingleResult(), is(32.923f) ); - assertThat( session.createQuery("select round(32.923451f,4)").getSingleResult(), is(32.9235f) ); + assertThat( session.createQuery("select trunc(32.92345f)", Float.class).getSingleResult(), is(32f) ); + assertThat( session.createQuery("select trunc(32.92345f,3)", Float.class).getSingleResult(), is(32.923f) ); + assertThat( session.createQuery("select trunc(-32.92345f)", Float.class).getSingleResult(), is(-32f) ); + assertThat( session.createQuery("select trunc(-32.92345f,3)", Float.class).getSingleResult(), is(-32.923f) ); + assertThat( session.createQuery("select truncate(32.92345f)", Float.class).getSingleResult(), is(32f) ); + assertThat( session.createQuery("select truncate(32.92345f,3)", Float.class).getSingleResult(), is(32.923f) ); + assertThat( session.createQuery("select round(32.92345f)", Float.class).getSingleResult(), is(33f) ); + assertThat( session.createQuery("select round(32.92345f,1)", Float.class).getSingleResult(), is(32.9f) ); + assertThat( session.createQuery("select round(32.92345f,3)", Float.class).getSingleResult(), is(32.923f) ); + assertThat( session.createQuery("select round(32.923451f,4)", Float.class).getSingleResult(), is(32.9235f) ); - assertThat( session.createQuery("select trunc(32.92345d)").getSingleResult(), is(32d) ); - assertThat( session.createQuery("select trunc(32.92345d,3)").getSingleResult(), is(32.923d) ); - assertThat( session.createQuery("select trunc(-32.92345d)").getSingleResult(), is(-32d) ); - assertThat( session.createQuery("select trunc(-32.92345d,3)").getSingleResult(), is(-32.923d) ); - assertThat( session.createQuery("select truncate(32.92345d)").getSingleResult(), is(32d) ); - assertThat( session.createQuery("select truncate(32.92345d,3)").getSingleResult(), is(32.923d) ); - assertThat( session.createQuery("select round(32.92345d)").getSingleResult(), is(33d) ); - assertThat( session.createQuery("select round(32.92345d,1)").getSingleResult(), is(32.9d) ); - assertThat( session.createQuery("select round(32.92345d,3)").getSingleResult(), is(32.923d) ); - assertThat( session.createQuery("select round(32.923451d,4)").getSingleResult(), is(32.9235d) ); + assertThat( session.createQuery("select trunc(32.92345d)", Double.class).getSingleResult(), is(32d) ); + assertThat( session.createQuery("select trunc(32.92345d,3)", Double.class).getSingleResult(), is(32.923d) ); + assertThat( session.createQuery("select trunc(-32.92345d)", Double.class).getSingleResult(), is(-32d) ); + assertThat( session.createQuery("select trunc(-32.92345d,3)", Double.class).getSingleResult(), is(-32.923d) ); + assertThat( session.createQuery("select truncate(32.92345d)", Double.class).getSingleResult(), is(32d) ); + assertThat( session.createQuery("select truncate(32.92345d,3)", Double.class).getSingleResult(), is(32.923d) ); + assertThat( session.createQuery("select round(32.92345d)", Double.class).getSingleResult(), is(33d) ); + assertThat( session.createQuery("select round(32.92345d,1)", Double.class).getSingleResult(), is(32.9d) ); + assertThat( session.createQuery("select round(32.92345d,3)", Double.class).getSingleResult(), is(32.923d) ); + assertThat( session.createQuery("select round(32.923451d,4)", Double.class).getSingleResult(), is(32.9235d) ); } ); } @@ -514,9 +518,7 @@ public void testRoundTruncFunctions(SessionFactoryScope scope) { @RequiresDialect(PostgreSQLDialect.class) public void testDateTruncFunction(SessionFactoryScope scope) { scope.inTransaction( - session -> { - session.createQuery("select date_trunc(year,current_timestamp)").getSingleResult(); - } + session -> session.createQuery("select date_trunc(year,current_timestamp)", Timestamp.class).getSingleResult() ); } @@ -524,19 +526,16 @@ public void testDateTruncFunction(SessionFactoryScope scope) { public void testLowerUpperFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select lower(e.theString), upper(e.theString) from EntityOfBasics e") + session.createQuery("select lower(e.theString), upper(e.theString) from EntityOfBasics e", Object[].class) .list(); - assertThat( session.createQuery("select lower('HELLO')").getSingleResult(), is("hello") ); - assertThat( session.createQuery("select upper('hello')").getSingleResult(), is("HELLO") ); + assertThat( session.createQuery("select lower('HELLO')", String.class).getSingleResult(), is("hello") ); + assertThat( session.createQuery("select upper('hello')", String.class).getSingleResult(), is("HELLO") ); } ); try { scope.inTransaction( - session -> { - session.createQuery("select upper(3)") - .list(); - } + session -> session.createQuery( "select upper(3)", String.class).list() ); fail(); } @@ -549,18 +548,15 @@ public void testLowerUpperFunctions(SessionFactoryScope scope) { public void testLengthFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select length(e.theString) from EntityOfBasics e where length(e.theString) > 1") + session.createQuery("select length(e.theString) from EntityOfBasics e where length(e.theString) > 1", Integer.class) .list(); - assertThat( session.createQuery("select length('hello')").getSingleResult(), is(5) ); + assertThat( session.createQuery("select length('hello')", Integer.class).getSingleResult(), is(5) ); } ); try { scope.inTransaction( - session -> { - session.createQuery("select length(3)") - .list(); - } + session -> session.createQuery( "select length(3)", Integer.class).list() ); fail(); } @@ -573,24 +569,21 @@ public void testLengthFunction(SessionFactoryScope scope) { public void testSubstringFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select substring(e.theString, e.theInt) from EntityOfBasics e") + session.createQuery("select substring(e.theString, e.theInt) from EntityOfBasics e", String.class) .list(); - session.createQuery("select substring(e.theString, 0, e.theInt) from EntityOfBasics e") + session.createQuery("select substring(e.theString, 0, e.theInt) from EntityOfBasics e", String.class) .list(); - session.createQuery("select substring(e.theString from e.theInt) from EntityOfBasics e") + session.createQuery("select substring(e.theString from e.theInt) from EntityOfBasics e", String.class) .list(); - session.createQuery("select substring(e.theString from 0 for e.theInt) from EntityOfBasics e") + session.createQuery("select substring(e.theString from 0 for e.theInt) from EntityOfBasics e", String.class) .list(); - assertThat( session.createQuery("select substring('hello world',4, 5)").getSingleResult(), is("lo wo") ); + assertThat( session.createQuery("select substring('hello world',4, 5)", String.class).getSingleResult(), is("lo wo") ); } ); try { scope.inTransaction( - session -> { - session.createQuery("select substring('hello world', 'world', 5)") - .list(); - } + session -> session.createQuery( "select substring('hello world', 'world', 5)", String.class).list() ); fail(); } @@ -603,10 +596,10 @@ public void testSubstringFunction(SessionFactoryScope scope) { public void testLeftRightFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select left(e.theString, e.theInt), right(e.theString, e.theInt) from EntityOfBasics e") + session.createQuery("select left(e.theString, e.theInt), right(e.theString, e.theInt) from EntityOfBasics e", Object[].class) .list(); - assertThat( session.createQuery("select left('hello world', 5)").getSingleResult(), is("hello") ); - assertThat( session.createQuery("select right('hello world', 5)").getSingleResult(), is("world") ); + assertThat( session.createQuery("select left('hello world', 5)", String.class).getSingleResult(), is("hello") ); + assertThat( session.createQuery("select right('hello world', 5)", String.class).getSingleResult(), is("world") ); } ); } @@ -615,9 +608,9 @@ public void testLeftRightFunctions(SessionFactoryScope scope) { public void testPositionFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select position('hello' in e.theString) from EntityOfBasics e") + session.createQuery("select position('hello' in e.theString) from EntityOfBasics e", Integer.class) .list(); - assertThat( session.createQuery("select position('world' in 'hello world')").getSingleResult(), is(7) ); + assertThat( session.createQuery("select position('world' in 'hello world')", Integer.class).getSingleResult(), is(7) ); } ); } @@ -626,11 +619,11 @@ public void testPositionFunction(SessionFactoryScope scope) { public void testLocateFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select locate('hello', e.theString) from EntityOfBasics e") + session.createQuery("select locate('hello', e.theString) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select locate('hello', e.theString, e.theInteger) from EntityOfBasics e") + session.createQuery("select locate('hello', e.theString, e.theInteger) from EntityOfBasics e", Integer.class) .list(); - assertThat( session.createQuery("select locate('world', 'hello world')").getSingleResult(), is(7) ); + assertThat( session.createQuery("select locate('world', 'hello world')", Integer.class).getSingleResult(), is(7) ); } ); } @@ -639,13 +632,13 @@ public void testLocateFunction(SessionFactoryScope scope) { public void testOverlayFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select overlay('hello world' placing 'goodbye' from 1 for 5) from EntityOfBasics") + assertThat( session.createQuery("select overlay('hello world' placing 'goodbye' from 1 for 5) from EntityOfBasics", String.class) .list().get(0), is("goodbye world") ); - assertThat( session.createQuery("select overlay('hello world' placing 'goodbye' from 7 for 5) from EntityOfBasics") + assertThat( session.createQuery("select overlay('hello world' placing 'goodbye' from 7 for 5) from EntityOfBasics", String.class) .list().get(0), is("hello goodbye") ); - assertThat( session.createQuery("select overlay('xxxxxx' placing 'yy' from 3) from EntityOfBasics") + assertThat( session.createQuery("select overlay('xxxxxx' placing 'yy' from 3) from EntityOfBasics", String.class) .list().get(0), is("xxyyxx") ); - assertThat( session.createQuery("select overlay('xxxxxx' placing ' yy ' from 3 for 2) from EntityOfBasics") + assertThat( session.createQuery("select overlay('xxxxxx' placing ' yy ' from 3 for 2) from EntityOfBasics", String.class) .list().get(0), is("xx yy xx") ); } ); @@ -655,20 +648,20 @@ public void testOverlayFunction(SessionFactoryScope scope) { public void testOverlayFunctionParameters(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select overlay(?1 placing 'yy' from 3)") + session.createQuery("select overlay(?1 placing 'yy' from 3)", String.class) .setParameter(1, "xxxxxx") .list(); - session.createQuery("select overlay('xxxxxx' placing ?1 from 3)") + session.createQuery("select overlay('xxxxxx' placing ?1 from 3)", String.class) .setParameter(1, "yy") .list(); - session.createQuery("select overlay('xxxxxx' placing 'yy' from ?1)") + session.createQuery("select overlay('xxxxxx' placing 'yy' from ?1)", String.class) .setParameter(1, 3) .list(); - session.createQuery("select overlay(?2 placing ?1 from 3)") + session.createQuery("select overlay(?2 placing ?1 from 3)", String.class) .setParameter(1, "yy") .setParameter(2, "xxxxxx") .list(); - session.createQuery("select overlay(:text placing :rep from 3)") + session.createQuery("select overlay(:text placing :rep from 3)", String.class) .setParameter("rep", "yy") .setParameter("text", "xxxxxx") .list(); @@ -681,19 +674,16 @@ public void testOverlayFunctionParameters(SessionFactoryScope scope) { public void testReplaceFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select replace(e.theString, 'hello', 'goodbye') from EntityOfBasics e") + session.createQuery("select replace(e.theString, 'hello', 'goodbye') from EntityOfBasics e", String.class) .list(); - assertThat( session.createQuery("select replace('hello world', 'hello', 'goodbye')").getSingleResult(), is("goodbye world") ); - assertThat( session.createQuery("select replace('hello world', 'o', 'ooo')").getSingleResult(), is("hellooo wooorld") ); + assertThat( session.createQuery("select replace('hello world', 'hello', 'goodbye')", String.class).getSingleResult(), is("goodbye world") ); + assertThat( session.createQuery("select replace('hello world', 'o', 'ooo')", String.class).getSingleResult(), is("hellooo wooorld") ); } ); try { scope.inTransaction( - session -> { - session.createQuery("select replace(e.theString, 1, 'goodbye') from EntityOfBasics e") - .list(); - } + session -> session.createQuery( "select replace(e.theString, 1, 'goodbye') from EntityOfBasics e", String.class).list() ); fail(); } @@ -706,25 +696,22 @@ public void testReplaceFunction(SessionFactoryScope scope) { public void testTrimFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select trim(leading ' ' from e.theString) from EntityOfBasics e") + session.createQuery("select trim(leading ' ' from e.theString) from EntityOfBasics e", String.class) .list(); - session.createQuery("select trim(trailing ' ' from e.theString) from EntityOfBasics e") + session.createQuery("select trim(trailing ' ' from e.theString) from EntityOfBasics e", String.class) .list(); - session.createQuery("select trim(both ' ' from e.theString) from EntityOfBasics e") + session.createQuery("select trim(both ' ' from e.theString) from EntityOfBasics e", String.class) .list(); - assertThat( session.createQuery("select trim(leading from ' hello')").getSingleResult(), is("hello") ); - assertThat( session.createQuery("select trim(trailing from 'hello ')").getSingleResult(), is("hello") ); - assertThat( session.createQuery("select trim(both from ' hello ')").getSingleResult(), is("hello") ); - assertThat( session.createQuery("select trim(both '-' from '---hello---')").getSingleResult(), is("hello") ); + assertThat( session.createQuery("select trim(leading from ' hello')", String.class).getSingleResult(), is("hello") ); + assertThat( session.createQuery("select trim(trailing from 'hello ')", String.class).getSingleResult(), is("hello") ); + assertThat( session.createQuery("select trim(both from ' hello ')", String.class).getSingleResult(), is("hello") ); + assertThat( session.createQuery("select trim(both '-' from '---hello---')", String.class).getSingleResult(), is("hello") ); } ); try { scope.inTransaction( - session -> { - session.createQuery("select trim(leading ' ' from 3)") - .list(); - } + session -> session.createQuery( "select trim(leading ' ' from 3)", String.class).list() ); fail(); } @@ -738,23 +725,20 @@ public void testTrimFunction(SessionFactoryScope scope) { public void testPadFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat(session.createQuery("select pad('hello' with 10 leading)").getSingleResult(), + assertThat(session.createQuery("select pad('hello' with 10 leading)", String.class).getSingleResult(), is(" hello")); - assertThat(session.createQuery("select pad('hello' with 10 trailing)").getSingleResult(), + assertThat(session.createQuery("select pad('hello' with 10 trailing)", String.class).getSingleResult(), is("hello ")); - assertThat(session.createQuery("select pad('hello' with 10 leading '.')").getSingleResult(), + assertThat(session.createQuery("select pad('hello' with 10 leading '.')", String.class).getSingleResult(), is(".....hello")); - assertThat(session.createQuery("select pad('hello' with 10 trailing '.')").getSingleResult(), + assertThat(session.createQuery("select pad('hello' with 10 trailing '.')", String.class).getSingleResult(), is("hello.....")); } ); try { scope.inTransaction( - session -> { - session.createQuery("select pad('hello' with ' ' leading)") - .list(); - } + session -> session.createQuery( "select pad('hello' with ' ' leading)", String.class).list() ); fail(); } @@ -763,10 +747,7 @@ public void testPadFunction(SessionFactoryScope scope) { } try { scope.inTransaction( - session -> { - session.createQuery("select pad(3 with 4 leading)") - .list(); - } + session -> session.createQuery( "select pad(3 with 4 leading)", String.class).list() ); fail(); } @@ -779,11 +760,11 @@ public void testPadFunction(SessionFactoryScope scope) { public void testPadFunctionParameters(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select pad(?1 with ?2 leading)") + session.createQuery("select pad(?1 with ?2 leading)", String.class) .setParameter(1, "hello") .setParameter(2, 10) .getSingleResult(); - session.createQuery("select pad(:string with :length leading)") + session.createQuery("select pad(:string with :length leading)", String.class) .setParameter("string", "hello") .setParameter("length", 10) .getSingleResult(); @@ -795,93 +776,93 @@ 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") ); + assertThat( ( session.createQuery("select cast(e.theBoolean as String) from EntityOfBasics e", String.class).getSingleResult()).toLowerCase(), is("false") ); + assertThat( ( session.createQuery("select cast(e.theNumericBoolean as String) from EntityOfBasics e", String.class).getSingleResult()).toLowerCase(), is("false") ); + assertThat( ( session.createQuery("select cast(e.theStringBoolean as String) from EntityOfBasics e", String.class).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") + session.createQuery("select cast(e.theDate as String), cast(e.theTime as String), cast(e.theTimestamp as String) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select cast(e.id as String), cast(e.theInt as String), cast(e.theDouble as String) from EntityOfBasics e") + session.createQuery("select cast(e.id as String), cast(e.theInt as String), cast(e.theDouble as String) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select cast(e.id as Float), cast(e.theInt as Double), cast(e.theDouble as Long) from EntityOfBasics e") + session.createQuery("select cast(e.id as Float), cast(e.theInt as Double), cast(e.theDouble as Long) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select cast(e.id as BigInteger(10)), cast(e.theDouble as BigDecimal(10,5)) from EntityOfBasics e") + session.createQuery("select cast(e.id as BigInteger(10)), cast(e.theDouble as BigDecimal(10,5)) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select cast(e.theString as String(15)), cast(e.theDouble as String(17)) from EntityOfBasics e") + session.createQuery("select cast(e.theString as String(15)), cast(e.theDouble as String(17)) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select cast('1002342345234523.452435245245243' as BigDecimal) from EntityOfBasics") + session.createQuery( "select cast('1002342345234523.452435245245243' as BigDecimal) from EntityOfBasics", BigDecimal.class) .list(); - session.createQuery("select cast('1002342345234523.452435245245243' as BigDecimal(30, 10)) from EntityOfBasics") + session.createQuery("select cast('1002342345234523.452435245245243' as BigDecimal(30, 10)) from EntityOfBasics", BigDecimal.class) .list(); - session.createQuery("select cast('1234234523452345243524524524' as BigInteger) from EntityOfBasics") + session.createQuery("select cast('1234234523452345243524524524' as BigInteger) from EntityOfBasics", BigInteger.class) .list(); - session.createQuery("select cast('1234234523452345243524524524' as BigInteger(30)) from EntityOfBasics") + session.createQuery("select cast('1234234523452345243524524524' as BigInteger(30)) from EntityOfBasics", BigInteger.class) .list(); - session.createQuery("select cast('3811234234.12312' as Double) from EntityOfBasics") + session.createQuery("select cast('3811234234.12312' as Double) from EntityOfBasics", Double.class) .list(); - session.createQuery("select cast('1234234' as Integer) from EntityOfBasics") + session.createQuery("select cast('1234234' as Integer) from EntityOfBasics", Integer.class) .list(); - session.createQuery("select cast(1 as Boolean), cast(0 as Boolean) from EntityOfBasics") + session.createQuery("select cast(1 as Boolean), cast(0 as Boolean) from EntityOfBasics", Object[].class) .list(); - session.createQuery("select cast('12:13:14' as Time) from EntityOfBasics") + session.createQuery("select cast('12:13:14' as Time) from EntityOfBasics", Time.class) .list(); - session.createQuery("select cast('1911-10-09' as Date) from EntityOfBasics") + session.createQuery("select cast('1911-10-09' as Date) from EntityOfBasics", Date.class) .list(); - session.createQuery("select cast('1911-10-09 12:13:14.123' as Timestamp) from EntityOfBasics") + session.createQuery("select cast('1911-10-09 12:13:14.123' as Timestamp) from EntityOfBasics", Timestamp.class) .list(); - session.createQuery("select cast('12:13:14' as LocalTime) from EntityOfBasics") + session.createQuery("select cast('12:13:14' as LocalTime) from EntityOfBasics", LocalTime.class) .list(); - session.createQuery("select cast('1911-10-09' as LocalDate) from EntityOfBasics") + session.createQuery("select cast('1911-10-09' as LocalDate) from EntityOfBasics", LocalDate.class) .list(); - session.createQuery("select cast('1911-10-09 12:13:14.123' as LocalDateTime) from EntityOfBasics") + session.createQuery("select cast('1911-10-09 12:13:14.123' as LocalDateTime) from EntityOfBasics", LocalDateTime.class) .list(); - assertThat( session.createQuery("select cast(1 as Boolean)").getSingleResult(), is(true) ); - assertThat( session.createQuery("select cast(0 as Boolean)").getSingleResult(), is(false) ); - assertThat( session.createQuery("select cast('1234' as Integer)").getSingleResult(), is(1234) ); - assertThat( session.createQuery("select cast('1234' as Short)").getSingleResult(), is((short) 1234) ); - assertThat( session.createQuery("select cast('123' as Byte)").getSingleResult(), is((byte) 123) ); - assertThat( session.createQuery("select cast('123' as Long)").getSingleResult(), is(123l) ); - assertThat( session.createQuery("select cast('123.12' as Float)").getSingleResult(), is(123.12f) ); + assertThat( session.createQuery("select cast(1 as Boolean)", Boolean.class).getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(0 as Boolean)", Boolean.class).getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast('1234' as Integer)", Integer.class).getSingleResult(), is(1234) ); + assertThat( session.createQuery("select cast('1234' as Short)", Short.class).getSingleResult(), is((short) 1234) ); + assertThat( session.createQuery("select cast('123' as Byte)", Byte.class).getSingleResult(), is((byte) 123) ); + assertThat( session.createQuery("select cast('123' as Long)", Long.class).getSingleResult(), is(123L) ); + assertThat( session.createQuery("select cast('123.12' as Float)", Float.class).getSingleResult(), is(123.12f) ); - assertThat( session.createQuery("select cast('hello' as String)").getSingleResult(), is("hello") ); - assertThat( ((String) session.createQuery("select cast(true as String)").getSingleResult()).toLowerCase(), is("true") ); - assertThat( ((String) session.createQuery("select cast(false as String)").getSingleResult()).toLowerCase(), is("false") ); - assertThat( session.createQuery("select cast(123 as String)").getSingleResult(), is("123") ); + assertThat( session.createQuery("select cast('hello' as String)", String.class).getSingleResult(), is("hello") ); + assertThat( ( session.createQuery("select cast(true as String)", String.class).getSingleResult()).toLowerCase(), is("true") ); + assertThat( ( session.createQuery("select cast(false as String)", String.class).getSingleResult()).toLowerCase(), is("false") ); + assertThat( session.createQuery("select cast(123 as String)", String.class).getSingleResult(), is("123") ); - assertThat( session.createQuery("select cast('1911-10-09' as LocalDate)").getSingleResult(), is(LocalDate.of(1911,10,9)) ); - assertThat( session.createQuery("select cast('12:13:14' as LocalTime)").getSingleResult(), is(LocalTime.of(12,13,14)) ); - assertThat( session.createQuery("select cast('1911-10-09 12:13:14' as LocalDateTime)").getSingleResult(), is(LocalDateTime.of(1911,10,9,12,13,14)) ); + assertThat( session.createQuery("select cast('1911-10-09' as LocalDate)", LocalDate.class).getSingleResult(), is(LocalDate.of(1911,10,9)) ); + assertThat( session.createQuery("select cast('12:13:14' as LocalTime)", LocalTime.class).getSingleResult(), is(LocalTime.of(12,13,14)) ); + assertThat( session.createQuery("select cast('1911-10-09 12:13:14' as LocalDateTime)", LocalDateTime.class).getSingleResult(), is(LocalDateTime.of(1911,10,9,12,13,14)) ); - assertThat( session.createQuery("select cast(local datetime as LocalTime)").getSingleResult(), instanceOf(LocalTime.class) ); - assertThat( session.createQuery("select cast(local datetime as LocalDate)").getSingleResult(), instanceOf(LocalDate.class) ); - assertThat( session.createQuery("select cast('1911-10-09 12:13:14.123' as LocalDateTime)").getSingleResult(), instanceOf(LocalDateTime.class) ); + assertThat( session.createQuery("select cast(local datetime as LocalTime)", LocalTime.class).getSingleResult(), instanceOf(LocalTime.class) ); + assertThat( session.createQuery("select cast(local datetime as LocalDate)", LocalDate.class).getSingleResult(), instanceOf(LocalDate.class) ); + assertThat( session.createQuery("select cast('1911-10-09 12:13:14.123' as LocalDateTime)", LocalDateTime.class).getSingleResult(), instanceOf(LocalDateTime.class) ); - assertThat( session.createQuery("select cast('12:13:14' as Time)").getSingleResult(), instanceOf(Time.class) ); - assertThat( session.createQuery("select cast('1911-10-09' as Date)").getSingleResult(), instanceOf(Date.class) ); - assertThat( session.createQuery("select cast('1911-10-09 12:13:14.123' as Timestamp)").getSingleResult(), instanceOf(Timestamp.class) ); + assertThat( session.createQuery("select cast('12:13:14' as Time)", Time.class).getSingleResult(), instanceOf(Time.class) ); + assertThat( session.createQuery("select cast('1911-10-09' as Date)", Date.class).getSingleResult(), instanceOf(Date.class) ); + assertThat( session.createQuery("select cast('1911-10-09 12:13:14.123' as Timestamp)", Timestamp.class).getSingleResult(), instanceOf(Timestamp.class) ); - 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(), anyOf( is("12:13:14"), is("12:13:14.0000"), is("12.13.14") ) ); - assertThat( (String) session.createQuery("select cast(datetime 1911-10-09 12:13:14 as String)").getSingleResult(), anyOf( startsWith("1911-10-09 12:13:14"), startsWith("1911-10-09-12.13.14") ) ); + assertThat( session.createQuery("select cast(date 1911-10-09 as String)", String.class).getSingleResult(), is("1911-10-09") ); + assertThat( session.createQuery("select cast(time 12:13:14 as String)", String.class).getSingleResult(), anyOf( is("12:13:14"), is("12:13:14.0000"), is("12.13.14") ) ); + assertThat( session.createQuery("select cast(datetime 1911-10-09 12:13:14 as String)", String.class).getSingleResult(), anyOf( startsWith("1911-10-09 12:13:14"), 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) ); + assertThat( session.createQuery("select cast(1 as NumericBoolean)", Boolean.class).getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(0 as NumericBoolean)", Boolean.class).getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast(true as YesNo)", Boolean.class).getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(false as YesNo)", Boolean.class).getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast(1 as YesNo)", Boolean.class).getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(0 as YesNo)", Boolean.class).getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast(true as TrueFalse)", Boolean.class).getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(false as TrueFalse)", Boolean.class).getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast(1 as TrueFalse)", Boolean.class).getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast(0 as TrueFalse)", Boolean.class).getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast('Y' as YesNo)", Boolean.class).getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast('N' as YesNo)", Boolean.class).getSingleResult(), is(false) ); + assertThat( session.createQuery("select cast('T' as TrueFalse)", Boolean.class).getSingleResult(), is(true) ); + assertThat( session.createQuery("select cast('F' as TrueFalse)", Boolean.class).getSingleResult(), is(false) ); } ); } @@ -890,12 +871,12 @@ public void testCastFunction(SessionFactoryScope scope) { public void testCastDoubleToString(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertThat( session.createQuery("select str(123.12)").getSingleResult(), is("123.12") ); - assertThat( session.createQuery("select cast(123.12 as String)").getSingleResult(), is("123.12") ); - assertThat( session.createQuery("select cast(123.12d as String)").getSingleResult(), is("123.12") ); - assertThat( session.createQuery("select cast(123.12f as String)").getSingleResult(), is("123.12") ); - assertThat( session.createQuery("select cast('123.12' as Double)").getSingleResult(), is(123.12d) ); - assertThat( session.createQuery("select cast('123.12' as Float)").getSingleResult(), is(123.12f) ); + assertThat( session.createQuery("select str(123.12)", String.class).getSingleResult(), is("123.12") ); + assertThat( session.createQuery("select cast(123.12 as String)", String.class).getSingleResult(), is("123.12") ); + assertThat( session.createQuery("select cast(123.12d as String)", String.class).getSingleResult(), is("123.12") ); + assertThat( session.createQuery("select cast(123.12f as String)", String.class).getSingleResult(), is("123.12") ); + assertThat( session.createQuery("select cast('123.12' as Double)", Double.class).getSingleResult(), is(123.12d) ); + assertThat( session.createQuery("select cast('123.12' as Float)", Float.class).getSingleResult(), is(123.12f) ); } ); } @@ -905,10 +886,10 @@ public void testCastDoubleToString(SessionFactoryScope scope) { public void testCastFunction_withTruncation(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select cast(e.theString as String(15)), cast(e.theDouble as String(8)) from EntityOfBasics e") + session.createQuery("select cast(e.theString as String(15)), cast(e.theDouble as String(8)) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select cast('ABCDEF' as Character) from EntityOfBasics") + session.createQuery("select cast('ABCDEF' as Character) from EntityOfBasics", Character.class) .list(); } ); @@ -919,9 +900,9 @@ public void testCastFunction_withTruncation(SessionFactoryScope scope) { public void testCastFunctionBinary(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select cast(e.theString as Binary) from EntityOfBasics e") + session.createQuery("select cast(e.theString as Binary) from EntityOfBasics e", byte[].class) .list(); - session.createQuery("select cast(e.theString as Binary(10)) from EntityOfBasics e") + session.createQuery("select cast(e.theString as Binary(10)) from EntityOfBasics e", byte[].class) .list(); } ); @@ -931,13 +912,13 @@ public void testCastFunctionBinary(SessionFactoryScope scope) { public void testStrFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select str(e.theDate), str(e.theTime), str(e.theTimestamp) from EntityOfBasics e") + session.createQuery("select str(e.theDate), str(e.theTime), str(e.theTimestamp) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select str(e.id), str(e.theInt), str(e.theDouble) from EntityOfBasics e") + session.createQuery("select str(e.id), str(e.theInt), str(e.theDouble) from EntityOfBasics e", Object[].class) .list(); - assertThat( session.createQuery("select str(69)").getSingleResult(), is("69") ); - assertThat( session.createQuery("select str(date 1911-10-09)").getSingleResult(), is("1911-10-09") ); - assertThat( session.createQuery("select str(time 12:13:14)").getSingleResult(), anyOf( is( "12:13:14"), is( "12:13:14.0000"), is( "12.13.14") ) ); + assertThat( session.createQuery("select str(69)", String.class).getSingleResult(), is("69") ); + assertThat( session.createQuery("select str(date 1911-10-09)", String.class).getSingleResult(), is("1911-10-09") ); + assertThat( session.createQuery("select str(time 12:13:14)", String.class).getSingleResult(), anyOf( is( "12:13:14"), is( "12:13:14.0000"), is( "12.13.14") ) ); } ); } @@ -946,31 +927,31 @@ public void testStrFunction(SessionFactoryScope scope) { public void testExtractFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select year(e.theDate) from EntityOfBasics e") + session.createQuery("select year(e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select month(e.theDate) from EntityOfBasics e") + session.createQuery("select month(e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select day(e.theDate) from EntityOfBasics e") + session.createQuery("select day(e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select hour(e.theTime) from EntityOfBasics e") + session.createQuery("select hour(e.theTime) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select minute(e.theTime) from EntityOfBasics e") + session.createQuery("select minute(e.theTime) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select second(e.theTime) from EntityOfBasics e") + session.createQuery("select second(e.theTime) from EntityOfBasics e", Float.class) .list(); - session.createQuery("select year(e.theTimestamp) from EntityOfBasics e") + session.createQuery("select year(e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select month(e.theTimestamp) from EntityOfBasics e") + session.createQuery("select month(e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select day(e.theTimestamp) from EntityOfBasics e") + session.createQuery("select day(e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select hour(e.theTimestamp) from EntityOfBasics e") + session.createQuery("select hour(e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select minute(e.theTimestamp) from EntityOfBasics e") + session.createQuery("select minute(e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select second(e.theTimestamp) from EntityOfBasics e") + session.createQuery("select second(e.theTimestamp) from EntityOfBasics e", Float.class) .list(); } ); @@ -981,12 +962,12 @@ public void testExtractFunctions(SessionFactoryScope scope) { public void testLeastGreatestFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select least(1, 2, e.theInt, -1), greatest(1, e.theInt, 2, -1) from EntityOfBasics e") + session.createQuery("select least(1, 2, e.theInt, -1), greatest(1, e.theInt, 2, -1) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select least(0.0, e.theDouble), greatest(0.0, e.theDouble, 2.0) from EntityOfBasics e") + session.createQuery("select least(0.0, e.theDouble), greatest(0.0, e.theDouble, 2.0) from EntityOfBasics e", Object[].class) .list(); - assertThat( session.createQuery("select least(1,2,-1,3,4)").getSingleResult(), is(-1) ); - assertThat( session.createQuery("select greatest(1,2,-1,30,4)").getSingleResult(), is(30) ); + assertThat( session.createQuery("select least(1,2,-1,3,4)", Integer.class).getSingleResult(), is(-1) ); + assertThat( session.createQuery("select greatest(1,2,-1,30,4)", Integer.class).getSingleResult(), is(30) ); } ); } @@ -995,11 +976,11 @@ public void testLeastGreatestFunctions(SessionFactoryScope scope) { public void testCountFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select count(*) from EntityOfBasics e") + session.createQuery("select count(*) from EntityOfBasics e", Long.class) .list(); - session.createQuery("select count(e) from EntityOfBasics e") + session.createQuery("select count(e) from EntityOfBasics e", Long.class) .list(); - session.createQuery("select count(distinct e) from EntityOfBasics e") + session.createQuery("select count(distinct e) from EntityOfBasics e", Long.class) .list(); } ); @@ -1009,18 +990,18 @@ public void testCountFunction(SessionFactoryScope scope) { public void testAggregateFunctions(SessionFactoryScope scope) { scope.inTransaction( 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") + 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", Object[].class) .list(); - session.createQuery("select avg(distinct e.theInt) from EntityOfBasics e") + session.createQuery("select avg(distinct e.theInt) from EntityOfBasics e", Double.class) .list(); - session.createQuery("select sum(distinct e.theInt) from EntityOfBasics e") + session.createQuery("select sum(distinct e.theInt) from EntityOfBasics e", Long.class) .list(); - session.createQuery("select any(e.theInt > 0), every(e.theInt > 0) from EntityOfBasics e") + session.createQuery("select any(e.theInt > 0), every(e.theInt > 0) from EntityOfBasics e", Object[].class) .list(); //not supported by grammar: // session.createQuery("select any(e.theBoolean), every(e.theBoolean) from EntityOfBasics e") // .list(); - session.createQuery("select some(e.theInt > 0), all(e.theInt > 0) from EntityOfBasics e") + session.createQuery("select some(e.theInt > 0), all(e.theInt > 0) from EntityOfBasics e", Object[].class) .list(); } ); @@ -1029,10 +1010,8 @@ public void testAggregateFunctions(SessionFactoryScope scope) { @Test public void testStatisticalFunctions(SessionFactoryScope scope) { scope.inTransaction( - session -> { - session.createQuery("select var_samp(e.theDouble), var_pop(abs(e.theDouble)), stddev_samp(e.theDouble), stddev_pop(e.theDouble) from EntityOfBasics e") - .list(); - } + session -> session.createQuery( "select var_samp(e.theDouble), var_pop(abs(e.theDouble)), stddev_samp(e.theDouble), stddev_pop(e.theDouble) from EntityOfBasics e", Object[].class) + .list() ); } @@ -1040,17 +1019,17 @@ public void testStatisticalFunctions(SessionFactoryScope scope) { public void testCurrentDateTimeFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select current time, current date, current timestamp from EntityOfBasics") + session.createQuery("select current time, current date, current timestamp from EntityOfBasics", Object[].class) .list(); - session.createQuery("select local time, local date, local datetime from EntityOfBasics") + session.createQuery("select local time, local date, local datetime from EntityOfBasics", Object[].class) .list(); - session.createQuery("from EntityOfBasics e where e.theDate > current_date and e.theTime > current_time and e.theTimestamp > current_timestamp") + session.createQuery("from EntityOfBasics e where e.theDate > current_date and e.theTime > current_time and e.theTimestamp > current_timestamp", EntityOfBasics.class) .list(); - session.createQuery("from EntityOfBasics e where e.theDate > local date and e.theTime > local time and e.theTimestamp > local datetime") + session.createQuery("from EntityOfBasics e where e.theDate > local date and e.theTime > local time and e.theTimestamp > local datetime", EntityOfBasics.class) .list(); - session.createQuery("select instant from EntityOfBasics") + session.createQuery("select instant from EntityOfBasics", Instant.class) .list(); - session.createQuery("select offset datetime from EntityOfBasics") + session.createQuery("select offset datetime from EntityOfBasics", OffsetDateTime.class) .list(); } ); @@ -1060,22 +1039,22 @@ public void testCurrentDateTimeFunctions(SessionFactoryScope scope) { public void testTimestampAddDiffFunctions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select function('timestampadd',month,2,current date) from EntityOfBasics e") + session.createQuery("select function('timestampadd',month,2,current date) from EntityOfBasics e", Date.class) .list(); - session.createQuery("select function('timestampdiff',hour,e.theTimestamp,current timestamp) from EntityOfBasics e") + session.createQuery("select function('timestampdiff',hour,e.theTimestamp,current timestamp) from EntityOfBasics e", Long.class) .list(); - session.createQuery("select timestampadd(month,2,current date) from EntityOfBasics e") + session.createQuery("select timestampadd(month,2,current date) from EntityOfBasics e", Date.class) .list(); - session.createQuery("select timestampdiff(hour,e.theTimestamp,current timestamp) from EntityOfBasics e") + session.createQuery("select timestampdiff(hour,e.theTimestamp,current timestamp) from EntityOfBasics e", Long.class) .list(); assertThat( - session.createQuery("select timestampadd(day, 5, local datetime)").getSingleResult(), + session.createQuery("select timestampadd(day, 5, local datetime)", LocalDateTime.class).getSingleResult(), is( instanceOf(LocalDateTime.class) ) ); assertThat( - session.createQuery("select timestampdiff(day, local datetime, local datetime)").getSingleResult(), + session.createQuery("select timestampdiff(day, local datetime, local datetime)", Long.class).getSingleResult(), is( instanceOf(Long.class) ) ); } @@ -1092,11 +1071,11 @@ public void testDateAddDiffFunctions(SessionFactoryScope scope) { session -> { //we treat dateadd + datediff as synonyms for timestampadd + timestampdiff on most dbs assertThat( - session.createQuery("select dateadd(day, 5, local datetime)").getSingleResult(), + session.createQuery("select dateadd(day, 5, local datetime)", LocalDateTime.class).getSingleResult(), is( instanceOf(LocalDateTime.class) ) ); assertThat( - session.createQuery("select datediff(day, local date, local datetime)").getSingleResult(), + session.createQuery("select datediff(day, local date, local datetime)", Long.class).getSingleResult(), is( instanceOf(Long.class) ) ); } @@ -1109,7 +1088,7 @@ public void testDatediffFunctionMySQL(SessionFactoryScope scope) { session -> { //on MySQL we make room for the native datediff function assertThat( - session.createQuery("select datediff(datetime 1999-07-19 00:00, datetime 1999-07-23 23:59)").getSingleResult(), + session.createQuery("select datediff(datetime 1999-07-19 00:00, datetime 1999-07-23 23:59)", Integer.class).getSingleResult(), is( instanceOf(Integer.class) ) ); } @@ -1120,34 +1099,34 @@ public void testDatediffFunctionMySQL(SessionFactoryScope scope) { public void testIntervalAddExpressions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select e.theDate + 1 year from EntityOfBasics e") + session.createQuery("select e.theDate + 1 year from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theDate + 2 month from EntityOfBasics e") + session.createQuery("select e.theDate + 2 month from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theDate + 7 day from EntityOfBasics e") + session.createQuery("select e.theDate + 7 day from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTime + 1 hour from EntityOfBasics e") + session.createQuery("select e.theTime + 1 hour from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTime + 59 minute from EntityOfBasics e") + session.createQuery("select e.theTime + 59 minute from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTime + 30 second from EntityOfBasics e") + session.createQuery("select e.theTime + 30 second from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTime + 300000000 nanosecond from EntityOfBasics e") + session.createQuery("select e.theTime + 300000000 nanosecond from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + 1 year from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 1 year from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + 2 month from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 2 month from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + 7 day from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 7 day from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + 1 hour from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 1 hour from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + 59 minute from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 59 minute from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + 30 second from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 30 second from EntityOfBasics e", Date.class) .list(); } @@ -1158,34 +1137,34 @@ public void testIntervalAddExpressions(SessionFactoryScope scope) { public void testIntervalSubExpressions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select e.theDate - 1 year from EntityOfBasics e") + session.createQuery("select e.theDate - 1 year from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theDate - 2 month from EntityOfBasics e") + session.createQuery("select e.theDate - 2 month from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theDate - 7 day from EntityOfBasics e") + session.createQuery("select e.theDate - 7 day from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTime - 1 hour from EntityOfBasics e") + session.createQuery("select e.theTime - 1 hour from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTime - 59 minute from EntityOfBasics e") + session.createQuery("select e.theTime - 59 minute from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTime - 30 second from EntityOfBasics e") + session.createQuery("select e.theTime - 30 second from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp - 1 year from EntityOfBasics e") + session.createQuery("select e.theTimestamp - 1 year from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp - 2 month from EntityOfBasics e") + session.createQuery("select e.theTimestamp - 2 month from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp - 7 day from EntityOfBasics e") + session.createQuery("select e.theTimestamp - 7 day from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp - 1 hour from EntityOfBasics e") + session.createQuery("select e.theTimestamp - 1 hour from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp - 59 minute from EntityOfBasics e") + session.createQuery("select e.theTimestamp - 59 minute from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp - 30 second from EntityOfBasics e") + session.createQuery("select e.theTimestamp - 30 second from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + 3.333e-3 second from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 3.333e-3 second from EntityOfBasics e", Date.class) .list(); } @@ -1196,13 +1175,13 @@ public void testIntervalSubExpressions(SessionFactoryScope scope) { public void testIntervalAddSubExpressions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select e.theTimestamp + 4 day - 1 week from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 4 day - 1 week from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp - 4 day + 2 hour from EntityOfBasics e") + session.createQuery("select e.theTimestamp - 4 day + 2 hour from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + (4 day - 1 week) from EntityOfBasics e") + session.createQuery("select e.theTimestamp + (4 day - 1 week) from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp - (4 day + 2 hour) from EntityOfBasics e") + session.createQuery("select e.theTimestamp - (4 day + 2 hour) from EntityOfBasics e", Date.class) .list(); } ); @@ -1212,28 +1191,28 @@ public void testIntervalAddSubExpressions(SessionFactoryScope scope) { public void testIntervalScaleExpressions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select e.theTimestamp + 3 * 1 week from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 3 * 1 week from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + 3 * (4 day - 1 week) from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 3 * (4 day - 1 week) from EntityOfBasics e", Date.class) .list(); - session.createQuery("select e.theTimestamp + 3.5 * (4 day - 1 week) from EntityOfBasics e") + session.createQuery("select e.theTimestamp + 3.5 * (4 day - 1 week) from EntityOfBasics e", Date.class) .list(); - session.createQuery("select 4 day by second from EntityOfBasics e") + session.createQuery("select 4 day by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (4 day + 2 hour) by second from EntityOfBasics e") + session.createQuery("select (4 day + 2 hour) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (2 * 4 day) by second from EntityOfBasics e") + session.createQuery("select (2 * 4 day) by second from EntityOfBasics e", Long.class) .list(); // session.createQuery("select (1 year - 1 month) by day from EntityOfBasics e") // .list(); - session.createQuery("select (2 * (e.theTimestamp - e.theTimestamp) + 3 * (4 day + 2 hour)) by second from EntityOfBasics e") + session.createQuery("select (2 * (e.theTimestamp - e.theTimestamp) + 3 * (4 day + 2 hour)) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select e.theDuration by second from EntityOfBasics e") + session.createQuery("select e.theDuration by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (2 * e.theDuration + 3 day) by hour from EntityOfBasics e") + session.createQuery("select (2 * e.theDuration + 3 day) by hour from EntityOfBasics e", Long.class) .list(); } ); @@ -1244,10 +1223,10 @@ public void testDurationCast(SessionFactoryScope scope) { scope.inTransaction( session -> { assertEquals( 3*1_000_000_000L + 23*1_000_000L, - session.createQuery("select cast(e.theDuration as Long) from EntityOfBasics e") + session.createQuery("select cast(e.theDuration as Long) from EntityOfBasics e", Long.class) .getSingleResult() ); assertEquals( 5*60*1_000_000_000L, - session.createQuery("select cast(5 minute as Long)") + session.createQuery("select cast(5 minute as Long)", Long.class) .getSingleResult() ); } ); @@ -1258,16 +1237,16 @@ public void testDurationBy(SessionFactoryScope scope) { scope.inTransaction( session -> { assertEquals( Duration.of(3, ChronoUnit.SECONDS).plus( Duration.of(23,ChronoUnit.MILLIS) ), - session.createQuery("select e.theDuration from EntityOfBasics e") + session.createQuery("select e.theDuration from EntityOfBasics e", Duration.class) .getSingleResult() ); assertEquals( 3L, - session.createQuery("select e.theDuration by second from EntityOfBasics e") + session.createQuery("select e.theDuration by second from EntityOfBasics e", Long.class) .getSingleResult() ); assertEquals( 0L, - session.createQuery("select e.theDuration by day from EntityOfBasics e") + session.createQuery("select e.theDuration by day from EntityOfBasics e", Long.class) .getSingleResult() ); assertEquals( 3_023_000_000L, - session.createQuery("select e.theDuration by nanosecond from EntityOfBasics e") + session.createQuery("select e.theDuration by nanosecond from EntityOfBasics e", Long.class) .getSingleResult() ); } ); @@ -1278,34 +1257,34 @@ public void testDurationLiterals(SessionFactoryScope scope) { scope.inTransaction( session -> { assertEquals( Duration.of(3, ChronoUnit.SECONDS).plus( Duration.of(23,ChronoUnit.MILLIS) ), - session.createQuery("select e.theDuration from EntityOfBasics e") + session.createQuery("select e.theDuration from EntityOfBasics e", Duration.class) .getSingleResult() ); assertEquals( Duration.of(3, ChronoUnit.SECONDS).plus( Duration.of(23,ChronoUnit.MILLIS).plus( Duration.of(21, ChronoUnit.SECONDS) ) ), - session.createQuery("select e.theDuration + 21 second from EntityOfBasics e") + session.createQuery("select e.theDuration + 21 second from EntityOfBasics e", Duration.class) .getSingleResult() ); assertEquals( Duration.of(3, ChronoUnit.SECONDS).plus( Duration.of(23,ChronoUnit.MILLIS).plus( Duration.of(2, ChronoUnit.DAYS) ) ), - session.createQuery("select e.theDuration + 2 day from EntityOfBasics e") + session.createQuery("select e.theDuration + 2 day from EntityOfBasics e", Duration.class) .getSingleResult() ); assertEquals( Duration.of(2, ChronoUnit.DAYS), - session.createQuery("select 2 day") + session.createQuery("select 2 day", Duration.class) .getSingleResult() ); assertEquals( Duration.of(5, ChronoUnit.SECONDS), - session.createQuery("select 5 second") + session.createQuery("select 5 second", Duration.class) .getSingleResult() ); assertEquals( Duration.of(5, ChronoUnit.SECONDS).plus(Duration.of(2, ChronoUnit.DAYS)), - session.createQuery("select 5 second + 2 day") + session.createQuery("select 5 second + 2 day", Duration.class) .getSingleResult() ); assertEquals( Duration.of(30, ChronoUnit.SECONDS), - session.createQuery("select 3*(10 second)") + session.createQuery("select 3*(10 second)", Duration.class) .getSingleResult() ); assertEquals( Duration.of(14, ChronoUnit.DAYS), - session.createQuery("select 2*(7 day)") + session.createQuery("select 2*(7 day)", Duration.class) .getSingleResult() ); assertEquals( Duration.of(15, ChronoUnit.SECONDS).plus(Duration.of(6, ChronoUnit.DAYS)), - session.createQuery("select 3*(5 second + 2 day)") + session.createQuery("select 3*(5 second + 2 day)", Duration.class) .getSingleResult() ); assertEquals( Duration.of(6, ChronoUnit.SECONDS).plus( Duration.of(46,ChronoUnit.MILLIS) ), - session.createQuery("select 2 * e.theDuration from EntityOfBasics e") + session.createQuery("select 2 * e.theDuration from EntityOfBasics e", Duration.class) .getSingleResult() ); } ); @@ -1363,43 +1342,43 @@ public void testDurationArithmeticWithLiterals(SessionFactoryScope scope) { scope.inTransaction( session -> { assertEquals( LocalDate.of(1974,3,25), - session.createQuery("select date 1974-03-23 + 2 day") + session.createQuery("select date 1974-03-23 + 2 day", LocalDate.class) .getSingleResult() ); assertEquals( LocalDateTime.of(1974,3,25,5,30,25), - session.createQuery("select datetime 1974-03-23 5:30:25 + 2 day") + session.createQuery("select datetime 1974-03-23 5:30:25 + 2 day", LocalDateTime.class) .getSingleResult() ); assertEquals( LocalDateTime.of(1974,3,25,5,30,25), - session.createQuery("select datetime 1974-03-25 5:30:46 - 21 second") + session.createQuery("select datetime 1974-03-25 5:30:46 - 21 second", LocalDateTime.class) .getSingleResult() ); assertEquals( LocalDateTime.of(1974,3,25,5,30,25), - session.createQuery("select datetime 1974-03-23 5:30:46 - 21 second + 2 day") + session.createQuery("select datetime 1974-03-23 5:30:46 - 21 second + 2 day", LocalDateTime.class) .getSingleResult() ); assertEquals( Duration.of(5, ChronoUnit.DAYS), - session.createQuery("select date 1974-03-25 - date 1974-03-20") + session.createQuery("select date 1974-03-25 - date 1974-03-20", Duration.class) .getSingleResult() ); assertEquals( 5L, - session.createQuery("select (date 1974-03-25 - date 1974-03-20) by day") + session.createQuery("select (date 1974-03-25 - date 1974-03-20) by day", Long.class) .getSingleResult() ); assertEquals( 5*24*60L, - session.createQuery("select (date 1974-03-25 - date 1974-03-20) by minute") + session.createQuery("select (date 1974-03-25 - date 1974-03-20) by minute", Long.class) .getSingleResult() ); assertEquals( Duration.of(25, ChronoUnit.SECONDS), - session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:30:00)") + session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:30:00)", Duration.class) .getSingleResult() ); assertEquals( 25L, - session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:30:00) by second") + session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:30:00) by second", Long.class) .getSingleResult() ); assertEquals( 25L*1000000000, - session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:30:00) by nanosecond") + session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:30:00) by nanosecond", Long.class) .getSingleResult() ); assertEquals( Duration.of(10, ChronoUnit.MINUTES), - session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:20:25)") + session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:20:25)", Duration.class) .getSingleResult() ); assertEquals( 10L, - session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:20:25) by minute") + session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:20:25) by minute", Long.class) .getSingleResult() ); assertEquals( 10L*60, - session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:20:25) by second") + session.createQuery("select (datetime 1974-03-23 5:30:25 - datetime 1974-03-23 5:20:25) by second", Long.class) .getSingleResult() ); // timestampadd() might not work for time on at least some dbs: // assertEquals( LocalTime.of(5,30,25), @@ -1413,37 +1392,37 @@ public void testDurationArithmeticWithLiterals(SessionFactoryScope scope) { public void testIntervalDiffExpressions(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select (e.theDate - e.theDate) by year from EntityOfBasics e") + session.createQuery("select (e.theDate - e.theDate) by year from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theDate - e.theDate) by month from EntityOfBasics e") + session.createQuery("select (e.theDate - e.theDate) by month from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theDate - e.theDate) by day from EntityOfBasics e") + session.createQuery("select (e.theDate - e.theDate) by day from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - e.theTimestamp) by hour from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - e.theTimestamp) by hour from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - e.theTimestamp) by minute from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - e.theTimestamp) by minute from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - e.theTimestamp) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - e.theTimestamp) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - e.theTimestamp + 4 day) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - e.theTimestamp + 4 day) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - (e.theTimestamp + 4 day)) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - (e.theTimestamp + 4 day)) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp + 4 day - e.theTimestamp) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp + 4 day - e.theTimestamp) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp + 4 day - 2 hour - e.theTimestamp) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp + 4 day - 2 hour - e.theTimestamp) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - e.theTimestamp + 4 day + 2 hour) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - e.theTimestamp + 4 day + 2 hour) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - (e.theTimestamp + 4 day + 2 hour)) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - (e.theTimestamp + 4 day + 2 hour)) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp + (4 day - 1 week) - e.theTimestamp) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp + (4 day - 1 week) - e.theTimestamp) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - e.theTimestamp + (4 day + 2 hour)) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - e.theTimestamp + (4 day + 2 hour)) by second from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - (e.theTimestamp + (4 day + 2 hour))) by second from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - (e.theTimestamp + (4 day + 2 hour))) by second from EntityOfBasics e", Long.class) .list(); @@ -1461,18 +1440,18 @@ public void testIntervalDiffExpressions(SessionFactoryScope scope) { public void testIntervalDiffExpressionsDifferentTypes(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select (e.theDate - e.theTimestamp) by year from EntityOfBasics e") + session.createQuery("select (e.theDate - e.theTimestamp) by year from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theDate - e.theTimestamp) by month from EntityOfBasics e") + session.createQuery("select (e.theDate - e.theTimestamp) by month from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theDate - e.theTimestamp) by day from EntityOfBasics e") + session.createQuery("select (e.theDate - e.theTimestamp) by day from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - e.theDate) by year from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - e.theDate) by year from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - e.theDate) by month from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - e.theDate) by month from EntityOfBasics e", Long.class) .list(); - session.createQuery("select (e.theTimestamp - e.theDate) by day from EntityOfBasics e") + session.createQuery("select (e.theTimestamp - e.theDate) by day from EntityOfBasics e", Long.class) .list(); } ); @@ -1482,59 +1461,59 @@ public void testIntervalDiffExpressionsDifferentTypes(SessionFactoryScope scope) public void testExtractFunction(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select extract(year from e.theDate) from EntityOfBasics e") + session.createQuery("select extract(year from e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(month from e.theDate) from EntityOfBasics e") + session.createQuery("select extract(month from e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(day from e.theDate) from EntityOfBasics e") + session.createQuery("select extract(day from e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(day of year from e.theDate) from EntityOfBasics e") + session.createQuery("select extract(day of year from e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(day of month from e.theDate) from EntityOfBasics e") + session.createQuery("select extract(day of month from e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(quarter from e.theDate) from EntityOfBasics e") + session.createQuery("select extract(quarter from e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(hour from e.theTime) from EntityOfBasics e") + session.createQuery("select extract(hour from e.theTime) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(minute from e.theTime) from EntityOfBasics e") + session.createQuery("select extract(minute from e.theTime) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(second from e.theTime) from EntityOfBasics e") + session.createQuery("select extract(second from e.theTime) from EntityOfBasics e", Float.class) .list(); - session.createQuery("select extract(nanosecond from e.theTime) from EntityOfBasics e") + session.createQuery("select extract(nanosecond from e.theTime) from EntityOfBasics e", Long.class) .list(); - session.createQuery("select extract(year from e.theTimestamp) from EntityOfBasics e") + session.createQuery("select extract(year from e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(month from e.theTimestamp) from EntityOfBasics e") + session.createQuery("select extract(month from e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(day from e.theTimestamp) from EntityOfBasics e") + session.createQuery("select extract(day from e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(hour from e.theTimestamp) from EntityOfBasics e") + session.createQuery("select extract(hour from e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(minute from e.theTimestamp) from EntityOfBasics e") + session.createQuery("select extract(minute from e.theTimestamp) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(second from e.theTimestamp) from EntityOfBasics e") + session.createQuery("select extract(second from e.theTimestamp) from EntityOfBasics e", Float.class) .list(); - session.createQuery("select extract(time from e.theTimestamp), extract(date from e.theTimestamp) from EntityOfBasics e") + session.createQuery("select extract(time from e.theTimestamp), extract(date from e.theTimestamp) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select extract(time from local datetime), extract(date from local datetime) from EntityOfBasics e") + session.createQuery("select extract(time from local datetime), extract(date from local datetime) from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select extract(week of month from current date) from EntityOfBasics e") + session.createQuery("select extract(week of month from current date) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(week of year from current date) from EntityOfBasics e") + session.createQuery("select extract(week of year from current date) from EntityOfBasics e", Integer.class) .list(); - assertThat( session.createQuery("select extract(year from date 1974-03-25)").getSingleResult(), is(1974) ); - assertThat( session.createQuery("select extract(month from date 1974-03-25)").getSingleResult(), is(3) ); - assertThat( session.createQuery("select extract(day from date 1974-03-25)").getSingleResult(), is(25) ); + assertThat( session.createQuery("select extract(year from date 1974-03-25)", Integer.class).getSingleResult(), is(1974) ); + assertThat( session.createQuery("select extract(month from date 1974-03-25)", Integer.class).getSingleResult(), is(3) ); + assertThat( session.createQuery("select extract(day from date 1974-03-25)", Integer.class).getSingleResult(), is(25) ); - assertThat( session.createQuery("select extract(hour from time 12:30)").getSingleResult(), is(12) ); - assertThat( session.createQuery("select extract(minute from time 12:30)").getSingleResult(), is(30) ); + assertThat( session.createQuery("select extract(hour from time 12:30)", Integer.class).getSingleResult(), is(12) ); + assertThat( session.createQuery("select extract(minute from time 12:30)", Integer.class).getSingleResult(), is(30) ); } ); } @@ -1543,10 +1522,10 @@ public void testExtractFunction(SessionFactoryScope scope) { public void testExtractFunctionWeek(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select extract(day of week from e.theDate) from EntityOfBasics e") + session.createQuery("select extract(day of week from e.theDate) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(week from e.theDate) from EntityOfBasics e") + session.createQuery("select extract(week from e.theDate) from EntityOfBasics e", Integer.class) .list(); } @@ -1558,9 +1537,9 @@ public void testExtractFunctionWeek(SessionFactoryScope scope) { public void testExtractFunctionTimeZone(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select extract(offset hour from e.theZonedDateTime) from EntityOfBasics e") + session.createQuery("select extract(offset hour from e.theZonedDateTime) from EntityOfBasics e", Integer.class) .list(); - session.createQuery("select extract(offset minute from e.theZonedDateTime) from EntityOfBasics e") + session.createQuery("select extract(offset minute from e.theZonedDateTime) from EntityOfBasics e", Integer.class) .list(); } ); @@ -1571,7 +1550,7 @@ public void testExtractFunctionTimeZone(SessionFactoryScope scope) { @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsFormat.class, comment = "We extract the offset with a format function") public void testExtractFunctionTimeZoneOffset(SessionFactoryScope scope) { scope.inTransaction( - session -> session.createQuery( "select extract(offset from e.theZonedDateTime) from EntityOfBasics e") + session -> session.createQuery( "select extract(offset from e.theZonedDateTime) from EntityOfBasics e", Integer.class) .list() ); } @@ -1581,98 +1560,98 @@ public void testExtractFunctionWithAssertions(SessionFactoryScope scope) { scope.inTransaction( session -> { assertThat( - session.createQuery("select extract(week of year from date 2019-01-01) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(week of year from date 2019-01-01) from EntityOfBasics", Integer.class).getResultList().get(0), is(1) ); assertThat( - session.createQuery("select extract(week of year from date 2019-01-05) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(week of year from date 2019-01-05) from EntityOfBasics", Integer.class).getResultList().get(0), is(1) ); assertThat( - session.createQuery("select extract(week of year from date 2019-01-06) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(week of year from date 2019-01-06) from EntityOfBasics", Integer.class).getResultList().get(0), is(2) ); assertThat( - session.createQuery("select extract(week of month from date 2019-05-01) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(week of month from date 2019-05-01) from EntityOfBasics", Integer.class).getResultList().get(0), is(1) ); assertThat( - session.createQuery("select extract(week of month from date 2019-05-04) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(week of month from date 2019-05-04) from EntityOfBasics", Integer.class).getResultList().get(0), is(1) ); assertThat( - session.createQuery("select extract(week of month from date 2019-05-05) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(week of month from date 2019-05-05) from EntityOfBasics", Integer.class).getResultList().get(0), is(2) ); assertThat( - session.createQuery("select extract(week from date 2019-05-27) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(week from date 2019-05-27) from EntityOfBasics", Integer.class).getResultList().get(0), is(22) ); assertThat( - session.createQuery("select extract(week from date 2019-06-02) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(week from date 2019-06-02) from EntityOfBasics", Integer.class).getResultList().get(0), is(22) ); assertThat( - session.createQuery("select extract(week from date 2019-06-03) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(week from date 2019-06-03) from EntityOfBasics", Integer.class).getResultList().get(0), is(23) ); assertThat( - session.createQuery("select extract(day of year from date 2019-05-30) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(day of year from date 2019-05-30) from EntityOfBasics", Integer.class).getResultList().get(0), is(150) ); assertThat( - session.createQuery("select extract(day of month from date 2019-05-27) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(day of month from date 2019-05-27) from EntityOfBasics", Integer.class).getResultList().get(0), is(27) ); assertThat( - session.createQuery("select extract(day from date 2019-05-31) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(day from date 2019-05-31) from EntityOfBasics", Integer.class).getResultList().get(0), is(31) ); assertThat( - session.createQuery("select extract(month from date 2019-05-31) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(month from date 2019-05-31) from EntityOfBasics", Integer.class).getResultList().get(0), is(5) ); assertThat( - session.createQuery("select extract(year from date 2019-05-31) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(year from date 2019-05-31) from EntityOfBasics", Integer.class).getResultList().get(0), is(2019) ); assertThat( - session.createQuery("select extract(quarter from date 2019-05-31) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(quarter from date 2019-05-31) from EntityOfBasics", Integer.class).getResultList().get(0), is(2) ); assertThat( - session.createQuery("select extract(day of week from date 2019-05-27) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(day of week from date 2019-05-27) from EntityOfBasics", Integer.class).getResultList().get(0), is(2) ); assertThat( - session.createQuery("select extract(day of week from date 2019-05-31) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(day of week from date 2019-05-31) from EntityOfBasics", Integer.class).getResultList().get(0), is(6) ); assertThat( - session.createQuery("select extract(second from time 14:12:10) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(second from time 14:12:10) from EntityOfBasics", Float.class).getResultList().get(0), is(10f) ); assertThat( - session.createQuery("select extract(minute from time 14:12:10) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(minute from time 14:12:10) from EntityOfBasics", Integer.class).getResultList().get(0), is(12) ); assertThat( - session.createQuery("select extract(hour from time 14:12:10) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(hour from time 14:12:10) from EntityOfBasics", Integer.class).getResultList().get(0), is(14) ); assertThat( - session.createQuery("select extract(date from local datetime) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(date from local datetime) from EntityOfBasics", LocalDate.class).getResultList().get(0), instanceOf(LocalDate.class) ); assertThat( - session.createQuery("select extract(time from local datetime) from EntityOfBasics").getResultList().get(0), + session.createQuery("select extract(time from local datetime) from EntityOfBasics", LocalTime.class).getResultList().get(0), instanceOf(LocalTime.class) ); } @@ -1684,13 +1663,13 @@ public void testExtractFunctionWithAssertions(SessionFactoryScope scope) { public void testFormat(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select format(e.theDate as 'dd/MM/yy'), format(e.theDate as 'EEEE, MMMM dd, yyyy') from EntityOfBasics e") + session.createQuery("select format(e.theDate as 'dd/MM/yy'), format(e.theDate as 'EEEE, MMMM dd, yyyy') from EntityOfBasics e", Object[].class) .list(); - session.createQuery("select format(e.theTimestamp as 'dd/MM/yyyy ''at'' HH:mm:ss') from EntityOfBasics e") + session.createQuery("select format(e.theTimestamp as 'dd/MM/yyyy ''at'' HH:mm:ss') from EntityOfBasics e", Date.class) .list(); assertThat( - session.createQuery("select format(theDate as 'EEEE, dd/MM/yyyy') from EntityOfBasics where id=123").getResultList().get(0), + session.createQuery("select format(theDate as 'EEEE, dd/MM/yyyy') from EntityOfBasics where id=123", String.class).getResultList().get(0), is("Monday, 25/03/1974") ); } @@ -1703,10 +1682,10 @@ public void testFormat(SessionFactoryScope scope) { public void testFormatTime(SessionFactoryScope scope) { scope.inTransaction( session -> { - session.createQuery("select format(e.theTime as 'hh:mm:ss a') from EntityOfBasics e") + session.createQuery("select format(e.theTime as 'hh:mm:ss a') from EntityOfBasics e", Date.class) .list(); assertThat( - session.createQuery("select format(theTime as '''Hello'', hh:mm:ss a') from EntityOfBasics where id=123").getResultList().get(0), + session.createQuery("select format(theTime as '''Hello'', hh:mm:ss a') from EntityOfBasics where id=123", String.class).getResultList().get(0), isOneOf( "Hello, 08:10:08 PM", "Hello, 08:10:08 pm" ) ); } @@ -1733,11 +1712,11 @@ public void testMedian(SessionFactoryScope scope) { public void testHyperbolic(SessionFactoryScope scope) { scope.inTransaction( session -> { - assertEquals( Math.sinh(1), (Double) session.createQuery("select sinh(e.theDouble) from EntityOfBasics e") + assertEquals( Math.sinh(1), session.createQuery("select sinh(e.theDouble) from EntityOfBasics e", Double.class) .getSingleResult(), 1e-6 ); - assertEquals( Math.cosh(1), (Double) session.createQuery("select cosh(e.theDouble) from EntityOfBasics e") + assertEquals( Math.cosh(1), session.createQuery("select cosh(e.theDouble) from EntityOfBasics e", Double.class) .getSingleResult(), 1e-6 ); - assertEquals( Math.tanh(1), (Double) session.createQuery("select tanh(e.theDouble) from EntityOfBasics e") + assertEquals( Math.tanh(1), session.createQuery("select tanh(e.theDouble) from EntityOfBasics e", Double.class) .getSingleResult(), 1e-6 ); } ); @@ -1747,10 +1726,8 @@ public void testHyperbolic(SessionFactoryScope scope) { @Test 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(); - } + session -> session.createQuery( "select max(e.theDouble), e.gender, e.theInt from EntityOfBasics e group by e.gender, e.theInt", Object[].class) + .list() ); } @@ -1758,10 +1735,8 @@ public void testGrouping(SessionFactoryScope scope) { @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsGroupByRollup.class) 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 -> session.createQuery( "select avg(e.theDouble), e.gender, e.theInt from EntityOfBasics e group by rollup(e.gender, e.theInt)", Object[].class) + .list() ); } @@ -1769,10 +1744,8 @@ public void testGroupByRollup(SessionFactoryScope scope) { @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(); - } + session -> session.createQuery( "select avg(e.theDouble), e.gender, e.theInt from EntityOfBasics e group by cube(e.gender, e.theInt)", Object[].class) + .list() ); } @@ -1781,27 +1754,27 @@ public void testIn(SessionFactoryScope scope) { scope.inTransaction( session -> { assertEquals( 1, - session.createQuery("select 1 where 1 in (:list)") + session.createQuery("select 1 where 1 in (:list)", Integer.class) .setParameterList("list",List.of(1,2)) .list().size() ); assertEquals( 0, - session.createQuery("select 1 where 1 in (:list)") + session.createQuery("select 1 where 1 in (:list)", Integer.class) .setParameterList("list",List.of(0,3,2)) .list().size() ); assertEquals( 0, - session.createQuery("select 1 where 1 in (:list)") + session.createQuery("select 1 where 1 in (:list)", Integer.class) .setParameterList("list",List.of()) .list().size() ); assertEquals( 1, - session.createQuery("select 1 where 1 in :list") + session.createQuery("select 1 where 1 in :list", Integer.class) .setParameterList("list",List.of(1,2)) .list().size() ); assertEquals( 0, - session.createQuery("select 1 where 1 in :list") + session.createQuery("select 1 where 1 in :list", Integer.class) .setParameterList("list",List.of(0,3,2)) .list().size() ); assertEquals( 0, - session.createQuery("select 1 where 1 in :list") + session.createQuery("select 1 where 1 in :list", Integer.class) .setParameterList("list",List.of()) .list().size() ); } From 4e03f320fb02d596dffe4806bec11fd8d0c7fd6c Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Wed, 21 Dec 2022 18:59:46 +0100 Subject: [PATCH 0046/1497] HHH-15898 Allow explicit configuration of the UDT column order --- .../org/hibernate/annotations/Struct.java | 8 ++ .../internal/AggregateComponentBinder.java | 16 ++++ .../AggregateComponentSecondPass.java | 94 ++++++++++++++----- .../dialect/PostgreSQLStructJdbcType.java | 8 +- .../java/org/hibernate/mapping/Component.java | 8 ++ .../metamodel/internal/EmbeddableHelper.java | 8 +- .../StructComponentInstantiatorTest.java | 2 +- 7 files changed, 111 insertions(+), 33 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Struct.java b/hibernate-core/src/main/java/org/hibernate/annotations/Struct.java index a7266436ccc2..b045ee365a7f 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Struct.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Struct.java @@ -47,4 +47,12 @@ * The name of the UDT (user defined type). */ String name(); + + /** + * The ordered set of attributes of the UDT, as they appear physically in the DDL. + * It is important to specify the attributes in the same order for JDBC interactions to work correctly. + * If the annotated type is a record, the order of record components is used as the default order. + * If no default order can be inferred, attributes are assumed to be in alphabetical order. + */ + String[] attributes() default {}; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java index 5b1284ef25c9..486cbfafcaa4 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentBinder.java @@ -55,6 +55,7 @@ public static void processAggregate( () -> new EmbeddableAggregateJavaType<>( component.getComponentClass(), structName ) ); component.setStructName( structName ); + component.setStructColumnNames( determineStructAttributeNames( inferredData, returnedClassOrElement ) ); // Determine the aggregate column BasicValueBinder basicValueBinder = new BasicValueBinder( BasicValueBinder.Kind.ATTRIBUTE, component, context ); @@ -135,6 +136,21 @@ private static String determineStructName( return null; } + private static String[] determineStructAttributeNames(PropertyData inferredData, XClass returnedClassOrElement) { + final XProperty property = inferredData.getProperty(); + if ( property != null ) { + final Struct struct = property.getAnnotation( Struct.class ); + if ( struct != null ) { + return struct.attributes(); + } + } + final Struct struct = returnedClassOrElement.getAnnotation( Struct.class ); + if ( struct != null ) { + return struct.attributes(); + } + return null; + } + private static boolean isAggregate(XProperty property, XClass returnedClass) { if ( property != null ) { final Struct struct = property.getAnnotation( Struct.class ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java index 12ff5820b87b..35132097d387 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AggregateComponentSecondPass.java @@ -31,6 +31,7 @@ import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.Selectable; import org.hibernate.mapping.UserDefinedType; import org.hibernate.mapping.Value; import org.hibernate.metamodel.internal.EmbeddableHelper; @@ -189,41 +190,57 @@ public void doSecondPass(Map persistentClasses) throws private void orderColumns(UserDefinedType userDefinedType) { final Class componentClass = component.getComponentClass(); final int[] originalOrder = component.sortProperties(); - final int[] propertyMappingIndex; - if ( ReflectHelper.isRecord( componentClass ) ) { - if ( originalOrder == null ) { - propertyMappingIndex = null; + final String[] structColumnNames = component.getStructColumnNames(); + if ( structColumnNames == null || structColumnNames.length == 0 ) { + final int[] propertyMappingIndex; + if ( ReflectHelper.isRecord( componentClass ) ) { + if ( originalOrder == null ) { + propertyMappingIndex = null; + } + else { + final String[] componentNames = ReflectHelper.getRecordComponentNames( componentClass ); + propertyMappingIndex = EmbeddableHelper.determineMappingIndex( + component.getPropertyNames(), + componentNames + ); + } } - else { - final String[] componentNames = ReflectHelper.getRecordComponentNames( componentClass ); - propertyMappingIndex = EmbeddableHelper.determinePropertyMappingIndex( + else if ( component.getInstantiatorPropertyNames() != null ) { + propertyMappingIndex = EmbeddableHelper.determineMappingIndex( component.getPropertyNames(), - componentNames + component.getInstantiatorPropertyNames() ); } - } - else { - if ( component.getInstantiatorPropertyNames() == null ) { - return; + else { + propertyMappingIndex = null; } - propertyMappingIndex = EmbeddableHelper.determinePropertyMappingIndex( - component.getPropertyNames(), - component.getInstantiatorPropertyNames() - ); - } - final ArrayList orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() ); - final List properties = component.getProperties(); - if ( propertyMappingIndex == null ) { - for ( Property property : properties ) { - addColumns( orderedColumns, property.getValue() ); + if ( propertyMappingIndex == null ) { + // If there is default ordering possible, assume alphabetical ordering + final ArrayList orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() ); + final List properties = component.getProperties(); + for ( Property property : properties ) { + addColumns( orderedColumns, property.getValue() ); + } + userDefinedType.reorderColumns( orderedColumns ); + } + else { + final ArrayList orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() ); + final List properties = component.getProperties(); + for ( final int propertyIndex : propertyMappingIndex ) { + addColumns( orderedColumns, properties.get( propertyIndex ).getValue() ); + } + userDefinedType.reorderColumns( orderedColumns ); } } else { - for ( final int propertyIndex : propertyMappingIndex ) { - addColumns( orderedColumns, properties.get( propertyIndex ).getValue() ); + final ArrayList orderedColumns = new ArrayList<>( userDefinedType.getColumnSpan() ); + for ( String structColumnName : structColumnNames ) { + if ( !addColumns( orderedColumns, component, structColumnName ) ) { + throw new MappingException( "Couldn't find column [" + structColumnName + "] that was defined in @Struct(attributes) in the component [" + component.getComponentClassName() + "]" ); + } } + userDefinedType.reorderColumns( orderedColumns ); } - userDefinedType.reorderColumns( orderedColumns ); } private static void addColumns(ArrayList orderedColumns, Value value) { @@ -243,6 +260,33 @@ private static void addColumns(ArrayList orderedColumns, Value value) { } } + private static boolean addColumns(ArrayList orderedColumns, Component component, String structColumnName) { + for ( Property property : component.getProperties() ) { + final Value value = property.getValue(); + if ( value instanceof Component ) { + final Component subComponent = (Component) value; + if ( subComponent.getAggregateColumn() == null ) { + if ( addColumns( orderedColumns, subComponent, structColumnName ) ) { + return true; + } + } + else if ( structColumnName.equals( subComponent.getAggregateColumn().getName() ) ) { + orderedColumns.add( subComponent.getAggregateColumn() ); + return true; + } + } + else { + for ( Selectable selectable : value.getSelectables() ) { + if ( selectable instanceof Column && structColumnName.equals( ( (Column) selectable ).getName() ) ) { + orderedColumns.add( (Column) selectable ); + return true; + } + } + } + } + return false; + } + private void validateSupportedColumnTypes(String basePath, Component component) { for ( Property property : component.getProperties() ) { final Value value = property.getValue(); diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructJdbcType.java index ca4a0e48d6f7..317f4780b500 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLStructJdbcType.java @@ -178,7 +178,7 @@ protected X fromString(String string, JavaType javaType, WrapperOptions o } assert end == string.length(); if ( returnEmbeddable ) { - final Object[] attributeValues = getAttributeValues( embeddableMappingType, array, options ); + final Object[] attributeValues = getAttributeValues( embeddableMappingType, orderMapping, array, options ); //noinspection unchecked return (X) embeddableMappingType.getRepresentationStrategy().getInstantiator().instantiate( () -> attributeValues, @@ -420,8 +420,9 @@ private int deserializeStruct( options ); if ( returnEmbeddable ) { - final Object[] attributeValues = getAttributeValues( + final Object[] attributeValues = structJdbcType.getAttributeValues( structJdbcType.embeddableMappingType, + structJdbcType.orderMapping, subValues, options ); @@ -827,6 +828,7 @@ private void serializeBasicTo( private Object[] getAttributeValues( EmbeddableMappingType embeddableMappingType, + int[] orderMapping, Object[] rawJdbcValues, WrapperOptions options) throws SQLException { final int numberOfAttributeMappings = embeddableMappingType.getNumberOfAttributeMappings(); @@ -879,7 +881,7 @@ private int injectAttributeValue( jdbcValueCount = embeddableMappingType.getJdbcValueCount(); final Object[] subJdbcValues = new Object[jdbcValueCount]; System.arraycopy( rawJdbcValues, jdbcIndex, subJdbcValues, 0, subJdbcValues.length ); - final Object[] subValues = getAttributeValues( embeddableMappingType, subJdbcValues, options ); + final Object[] subValues = getAttributeValues( embeddableMappingType, null, subJdbcValues, options ); attributeValues[attributeIndex] = embeddableMappingType.getRepresentationStrategy() .getInstantiator() .instantiate( diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java index f10f8eedd79f..8508d33fec17 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Component.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Component.java @@ -82,6 +82,7 @@ public class Component extends SimpleValue implements MetaAttributable, Sortable private AggregateColumn aggregateColumn; private AggregateColumn parentAggregateColumn; private String structName; + private String[] structColumnNames; // lazily computed based on 'properties' field: invalidate by setting to null when properties are modified private transient List cachedSelectables; // lazily computed based on 'properties' field: invalidate by setting to null when properties are modified @@ -807,4 +808,11 @@ public void setInstantiator(Constructor instantiator, String[] instantiatorPr this.instantiatorPropertyNames = instantiatorPropertyNames; } + public String[] getStructColumnNames() { + return structColumnNames; + } + + public void setStructColumnNames(String[] structColumnNames) { + this.structColumnNames = structColumnNames; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableHelper.java index f5d97438f3b3..2ff922a3fe57 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/EmbeddableHelper.java @@ -9,11 +9,11 @@ import java.util.Arrays; public class EmbeddableHelper { - public static int[] determinePropertyMappingIndex(String[] propertyNames, String[] componentNames) { - final int[] index = new int[propertyNames.length]; + public static int[] determineMappingIndex(String[] sortedNames, String[] names) { + final int[] index = new int[sortedNames.length]; int i = 0; - for ( String componentName : componentNames ) { - final int mappingIndex = Arrays.binarySearch( propertyNames, componentName ); + for ( String name : names ) { + final int mappingIndex = Arrays.binarySearch( sortedNames, name ); if ( mappingIndex != -1 ) { index[i++] = mappingIndex; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentInstantiatorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentInstantiatorTest.java index fc036aa0b611..57236127e8b9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentInstantiatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/component/StructComponentInstantiatorTest.java @@ -238,7 +238,6 @@ public static class RecordStructHolder { @Id private Long id; - @Struct(name = "my_point_type") private Point thePoint; public RecordStructHolder() { @@ -268,6 +267,7 @@ public void setThePoint(Point aggregate) { } @Embeddable + @Struct(name = "my_point_type", attributes = { "y", "x", "z" }) public static class Point { private final String y; From e8ec1cdf9842eb3394f705e529498f9401649b64 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 7 Feb 2023 15:56:42 +0100 Subject: [PATCH 0047/1497] HHH-16136 Add test for issue --- .../OneToManyLazyAndEagerTest.java | 184 +++++++++++++++++ .../query/OneToManyLazyAndEagerProxyTest.java | 189 ++++++++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/association/OneToManyLazyAndEagerTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/OneToManyLazyAndEagerProxyTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/association/OneToManyLazyAndEagerTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/association/OneToManyLazyAndEagerTest.java new file mode 100644 index 000000000000..454ca7e27211 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/association/OneToManyLazyAndEagerTest.java @@ -0,0 +1,184 @@ +/* + * 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.orm.test.bytecode.enhancement.association; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.Hibernate; +import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Marco Belladelli + * @author Tomas Cerskus + */ +@RunWith(BytecodeEnhancerRunner.class) +@TestForIssue(jiraKey = "HHH-16136") +public class OneToManyLazyAndEagerTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + User.class, + Order.class, + OrderItem.class + }; + } + + @Before + public void prepare() { + doInJPA( this::entityManagerFactory, em -> { + final User user = new User( "User 1", "Marco" ); + final User targetUser = new User( "User 2", "Andrea" ); + final Order order = new Order( "Order 1", user, targetUser ); + final OrderItem orderItem = new OrderItem( "Order Item 1", user, order ); + order.getOrderItems().add( orderItem ); + em.persist( user ); + em.persist( targetUser ); + em.persist( order ); + em.persist( orderItem ); + } ); + } + + @After + public void tearDown() { + doInJPA( this::entityManagerFactory, em -> { + em.createQuery( "delete from OrderItem" ).executeUpdate(); + em.createQuery( "delete from Order" ).executeUpdate(); + em.createQuery( "delete from User" ).executeUpdate(); + } ); + } + + @Test + public void testQuery() { + doInJPA( this::entityManagerFactory, em -> { + final Order order = em.createQuery( "select o from Order o", Order.class ) + .getResultList() + .get( 0 ); + final User user = order.getUser(); + assertTrue( "Proxy should be initialized", Hibernate.isInitialized( user ) ); + assertEquals( "Marco", order.getUser().getName() ); + } ); + } + + @Entity(name = "Order") + @Table(name = "Orders") + public static class Order { + @Id + private String id; + + @OneToMany(targetEntity = OrderItem.class, mappedBy = "order", fetch = FetchType.EAGER) + private final Set orderItems = new HashSet<>(); + + @ManyToOne(fetch = FetchType.EAGER) + private User user; + + @ManyToOne(fetch = FetchType.EAGER) + private User targetUser; + + public Order() { + } + + public Order(String id, User user, User targetUser) { + this.id = id; + this.user = user; + this.targetUser = targetUser; + } + + public String getId() { + return id; + } + + public Set getOrderItems() { + return orderItems; + } + + public User getUser() { + return user; + } + + public User getTargetUser() { + return targetUser; + } + } + + @Entity(name = "User") + @Table(name = "Users") + public static class User { + @Id + private String id; + + private String name; + + public User() { + } + + public User(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } + + @Entity(name = "OrderItem") + @Table(name = "OrderItems") + public static class OrderItem { + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + private Order order; + + public OrderItem() { + } + + public OrderItem(String id, User user, Order order) { + this.id = id; + this.user = user; + this.order = order; + } + + public String getId() { + return id; + } + + public User getUser() { + return user; + } + + public Order getOrder() { + return order; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/OneToManyLazyAndEagerProxyTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/OneToManyLazyAndEagerProxyTest.java new file mode 100644 index 000000000000..aee48e4b9e06 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/OneToManyLazyAndEagerProxyTest.java @@ -0,0 +1,189 @@ +/* + * 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.orm.test.query; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.Hibernate; +import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; +import org.hibernate.proxy.HibernateProxy; + +import org.hibernate.testing.TestForIssue; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Marco Belladelli + * @author Tomas Cerskus + */ +@TestForIssue(jiraKey = "HHH-16136") +public class OneToManyLazyAndEagerProxyTest extends BaseEntityManagerFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + User.class, + Order.class, + OrderItem.class + }; + } + + @Before + public void prepare() { + doInJPA( this::entityManagerFactory, em -> { + final User user = new User( "User 1", "Marco" ); + final User targetUser = new User( "User 2", "Andrea" ); + final Order order = new Order( "Order 1", user, targetUser ); + final OrderItem orderItem = new OrderItem( "Order Item 1", user, order ); + order.getOrderItems().add( orderItem ); + em.persist( user ); + em.persist( targetUser ); + em.persist( order ); + em.persist( orderItem ); + } ); + } + + @After + public void tearDown() { + doInJPA( this::entityManagerFactory, em -> { + em.createQuery( "delete from OrderItem" ).executeUpdate(); + em.createQuery( "delete from Order" ).executeUpdate(); + em.createQuery( "delete from User" ).executeUpdate(); + }); + } + + @Test + public void testHibernateProxy() { + doInJPA( this::entityManagerFactory, em -> { + // get reference to get a lazy proxy instance + final User userRef = em.getReference( User.class, "User 1" ); + assertTrue( userRef instanceof HibernateProxy ); + assertFalse( "Proxy should not be initialized", Hibernate.isInitialized( userRef ) ); + // query eager order.user and check that same instance was initialized + final Order order = em.createQuery( "select o from Order o", Order.class ) + .getResultList() + .get( 0 ); + final User user = order.getUser(); + assertEquals( "Proxy instance should be the same", userRef, user ); + assertTrue( "Proxy should be initialized", Hibernate.isInitialized( user ) ); + assertEquals( "Marco", user.getName() ); + } ); + } + + @Entity(name = "Order") + @Table(name = "Orders") + public static class Order { + @Id + private String id; + + @OneToMany(targetEntity = OrderItem.class, mappedBy = "order", fetch = FetchType.EAGER) + private final Set orderItems = new HashSet<>(); + + @ManyToOne(fetch = FetchType.EAGER) + private User user; + + @ManyToOne(fetch = FetchType.EAGER) + private User targetUser; + + public Order() { + } + + public Order(String id, User user, User targetUser) { + this.id = id; + this.user = user; + this.targetUser = targetUser; + } + + public String getId() { + return id; + } + + public Set getOrderItems() { + return orderItems; + } + + public User getUser() { + return user; + } + + public User getTargetUser() { + return targetUser; + } + } + + @Entity(name = "User") + @Table(name = "Users") + public static class User { + @Id + private String id; + + private String name; + + public User() { + } + + public User(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } + + @Entity(name = "OrderItem") + @Table(name = "OrderItems") + public static class OrderItem { + @Id + private String id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + private User user; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + private Order order; + + public OrderItem() { + } + + public OrderItem(String id, User user, Order order) { + this.id = id; + this.user = user; + this.order = order; + } + + public String getId() { + return id; + } + + public User getUser() { + return user; + } + + public Order getOrder() { + return order; + } + } +} \ No newline at end of file From 5ececc8ea99dd7764d3f0571fc5ede375330b7f0 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Wed, 8 Feb 2023 16:19:26 +0100 Subject: [PATCH 0048/1497] HHH-16136 Initialize bytecode-enhanced proxy for associated entities --- .../entity/internal/EntitySelectFetchInitializer.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java index 683d7a6040b1..c9ac02228568 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java @@ -10,8 +10,10 @@ import org.hibernate.FetchNotFoundException; import org.hibernate.annotations.NotFoundAction; +import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.log.LoggingHelper; import org.hibernate.internal.util.StringHelper; @@ -31,6 +33,8 @@ import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; +import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.internal.log.LoggingHelper.toLoggableString; /** @@ -134,6 +138,12 @@ public void initializeInstance(RowProcessingState rowProcessingState) { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); entityInstance = persistenceContext.getEntity( entityKey ); if ( entityInstance != null ) { + if ( isPersistentAttributeInterceptable( entityInstance ) ) { + final PersistentAttributeInterceptor interceptor = asPersistentAttributeInterceptable( entityInstance ).$$_hibernate_getInterceptor(); + if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) { + ( (EnhancementAsProxyLazinessInterceptor) interceptor ).forceInitialize( entityInstance, null ); + } + } isInitialized = true; return; } From 1ab190134ed4893df27aeb011d8dd4c446353287 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Wed, 8 Feb 2023 15:50:33 +0100 Subject: [PATCH 0049/1497] remove an obsolete constructor no longer called by Quarkus and delete the stupid InformixDialectTestCase --- .../dialect/InformixDialectTestCase.java | 124 ------------------ .../org/hibernate/query/spi/QueryEngine.java | 68 ---------- 2 files changed, 192 deletions(-) delete mode 100644 hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/InformixDialectTestCase.java diff --git a/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/InformixDialectTestCase.java b/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/InformixDialectTestCase.java deleted file mode 100644 index 0ef6f369a7cd..000000000000 --- a/hibernate-community-dialects/src/test/java/org/hibernate/community/dialect/InformixDialectTestCase.java +++ /dev/null @@ -1,124 +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 . - */ -package org.hibernate.community.dialect; - -import org.hibernate.boot.registry.StandardServiceRegistry; -import org.hibernate.boot.registry.StandardServiceRegistryBuilder; -import org.hibernate.metamodel.model.domain.internal.JpaMetamodelImpl; -import org.hibernate.metamodel.model.domain.internal.MappingMetamodelImpl; -import org.hibernate.metamodel.spi.MappingMetamodelImplementor; -import org.hibernate.query.criteria.ValueHandlingMode; -import org.hibernate.query.internal.NamedObjectRepositoryImpl; -import org.hibernate.query.spi.QueryEngine; -import org.hibernate.query.sqm.function.SelfRenderingSqmFunction; -import org.hibernate.query.sqm.function.SqmFunctionDescriptor; -import org.hibernate.sql.ast.spi.SqlAppender; -import org.hibernate.sql.ast.spi.StringBuilderSqlAppender; -import org.hibernate.testing.boot.MetadataBuildingContextTestingImpl; -import org.hibernate.type.BasicType; -import org.hibernate.type.descriptor.java.JdbcDateJavaType; -import org.hibernate.type.descriptor.java.JdbcTimestampJavaType; -import org.hibernate.type.descriptor.jdbc.DateJdbcType; -import org.hibernate.type.descriptor.jdbc.TimestampJdbcType; -import org.hibernate.type.spi.TypeConfiguration; - -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.junit4.BaseUnitTestCase; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -import static java.util.Collections.emptyList; -import static java.util.Collections.emptyMap; -import static org.hibernate.engine.query.internal.NativeQueryInterpreterStandardImpl.NATIVE_QUERY_INTERPRETER; -import static org.junit.Assert.assertEquals; - -/** - * Testing of patched support for Informix boolean type; see HHH-9894, HHH-10800 - * - * @author Greg Jones - */ -public class InformixDialectTestCase extends BaseUnitTestCase { - - private static final InformixDialect dialect = new InformixDialect(); - private static StandardServiceRegistry ssr; - private static QueryEngine queryEngine; - private static MappingMetamodelImplementor mappingMetamodel; - private static TypeConfiguration typeConfiguration; - - @BeforeClass - public static void init() { - ssr = new StandardServiceRegistryBuilder().build(); - typeConfiguration = new TypeConfiguration(); - typeConfiguration.scope( new MetadataBuildingContextTestingImpl( ssr ) ); - mappingMetamodel = new MappingMetamodelImpl( typeConfiguration, ssr ); - final JpaMetamodelImpl jpaMetamodel = new JpaMetamodelImpl( typeConfiguration, mappingMetamodel, ssr ); - queryEngine = new QueryEngine( - null, - null, - jpaMetamodel, - ValueHandlingMode.BIND, - dialect.getPreferredSqlTypeCodeForBoolean(), - false, - new NamedObjectRepositoryImpl( emptyMap(), emptyMap(), emptyMap(), emptyMap() ), - NATIVE_QUERY_INTERPRETER, - dialect, - ssr - ); - } - - @AfterClass - public static void tearDown() { - queryEngine.close(); - ssr.close(); - } - - @Test - @TestForIssue(jiraKey = "HHH-9894") - public void testToBooleanValueStringTrue() { - assertEquals( "'t'", dialect.toBooleanValueString( true ) ); - } - - @Test - @TestForIssue(jiraKey = "HHH-9894") - public void testToBooleanValueStringFalse() { - assertEquals( "'f'", dialect.toBooleanValueString( false ) ); - } - - @Test - @TestForIssue(jiraKey = "HHH-10800") - public void testCurrentTimestampFunction() { - SqmFunctionDescriptor functionDescriptor = queryEngine.getSqmFunctionRegistry() - .findFunctionDescriptor( "current_timestamp" ); - SelfRenderingSqmFunction sqmExpression = - functionDescriptor.generateSqmExpression( null, queryEngine, typeConfiguration ); - BasicType basicType = (BasicType) sqmExpression.getNodeType(); - assertEquals( JdbcTimestampJavaType.INSTANCE, basicType.getJavaTypeDescriptor() ); - assertEquals( TimestampJdbcType.INSTANCE, basicType.getJdbcType() ); - - SqlAppender appender = new StringBuilderSqlAppender(); - sqmExpression.getRenderingSupport().render( appender, emptyList(), null ); - assertEquals( "current", appender.toString() ); - } - - @Test - @TestForIssue(jiraKey = "HHH-10800") - public void testCurrentDateFunction() { - SqmFunctionDescriptor functionDescriptor = queryEngine.getSqmFunctionRegistry() - .findFunctionDescriptor( "current_date" ); - SelfRenderingSqmFunction sqmExpression = - functionDescriptor.generateSqmExpression( null, queryEngine, typeConfiguration ); - BasicType basicType = (BasicType) sqmExpression.getNodeType(); - assertEquals( JdbcDateJavaType.INSTANCE, basicType.getJavaTypeDescriptor() ); - assertEquals( DateJdbcType.INSTANCE, basicType.getJdbcType() ); - - SqlAppender appender = new StringBuilderSqlAppender(); - sqmExpression.getRenderingSupport().render( appender, emptyList(), null ); - assertEquals( "today", appender.toString() ); - } - -} diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngine.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngine.java index 3426ecd283c2..2832dd6cb59f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngine.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryEngine.java @@ -9,7 +9,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.Supplier; import org.hibernate.Incubating; @@ -21,20 +20,16 @@ import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.query.spi.NativeQueryInterpreter; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.util.config.ConfigurationHelper; -import org.hibernate.metamodel.model.domain.spi.JpaMetamodelImplementor; -import org.hibernate.query.criteria.ValueHandlingMode; import org.hibernate.query.hql.HqlTranslator; import org.hibernate.query.hql.internal.StandardHqlTranslator; import org.hibernate.query.hql.spi.SqmCreationOptions; import org.hibernate.query.internal.QueryInterpretationCacheDisabledImpl; import org.hibernate.query.internal.QueryInterpretationCacheStandardImpl; import org.hibernate.query.named.NamedObjectRepository; -import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.function.SqmFunctionRegistry; import org.hibernate.query.sqm.internal.SqmCreationOptionsStandard; import org.hibernate.query.sqm.internal.SqmCriteriaNodeBuilder; @@ -150,69 +145,6 @@ private QueryEngine( ); } - /** - * Simplified constructor mainly meant for Quarkus use - */ - public QueryEngine( - String uuid, - String name, - JpaMetamodelImplementor jpaMetamodel, - ValueHandlingMode criteriaValueHandlingMode, - int preferredSqlTypeCodeForBoolean, - boolean useStrictJpaCompliance, - NamedObjectRepository namedObjectRepository, - NativeQueryInterpreter nativeQueryInterpreter, - Dialect dialect, - ServiceRegistry serviceRegistry) { - this.namedObjectRepository = Objects.requireNonNull( namedObjectRepository ); - this.sqmTranslatorFactory = null; - this.nativeQueryInterpreter = Objects.requireNonNull( nativeQueryInterpreter ); - this.sqmFunctionRegistry = new SqmFunctionRegistry(); - this.typeConfiguration = jpaMetamodel.getTypeConfiguration(); - - dialect.contributeFunctions( new FunctionContributionsImpl( serviceRegistry, typeConfiguration, sqmFunctionRegistry ) ); - - this.interpretationCache = buildInterpretationCache( - () -> serviceRegistry.getService( StatisticsImplementor.class ), - serviceRegistry.getService( ConfigurationService.class ).getSettings() - ); - - this.criteriaBuilder = new SqmCriteriaNodeBuilder( - uuid, - name, - this, - useStrictJpaCompliance, - criteriaValueHandlingMode, - serviceRegistry, - typeConfiguration::getSessionFactory - ); - - this.hqlTranslator = new StandardHqlTranslator( - new SqmCreationContext() { - @Override - public JpaMetamodelImplementor getJpaMetamodel() { - return jpaMetamodel; - } - - @Override - public ServiceRegistry getServiceRegistry() { - return serviceRegistry; - } - - @Override - public QueryEngine getQueryEngine() { - return QueryEngine.this; - } - - @Override - public NodeBuilder getNodeBuilder() { - return criteriaBuilder; - } - }, - () -> useStrictJpaCompliance - ); - } - private static HqlTranslator resolveHqlTranslator( QueryEngineOptions runtimeOptions, Dialect dialect, From b8dfc42efcd86a676ce79e826832f1474cff5626 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 2 Feb 2023 14:06:46 +0100 Subject: [PATCH 0050/1497] HHH-16112 Add test for issue --- .../OneToManyOrphanRemovalBatchTest.java | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/OneToManyOrphanRemovalBatchTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/OneToManyOrphanRemovalBatchTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/OneToManyOrphanRemovalBatchTest.java new file mode 100644 index 000000000000..682c3c55e203 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/orphan/onetomany/OneToManyOrphanRemovalBatchTest.java @@ -0,0 +1,117 @@ +/* + * 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.orm.test.jpa.orphan.onetomany; + +import java.util.List; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + OneToManyOrphanRemovalBatchTest.EntityA.class, + OneToManyOrphanRemovalBatchTest.EntityB.class +}) +@ServiceRegistry(settings = @Setting(name = AvailableSettings.STATEMENT_BATCH_SIZE, value = "5")) +@JiraKey("HHH-16112") +public class OneToManyOrphanRemovalBatchTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + EntityB entityB1 = new EntityB( "entity_b_1" ); + EntityB entityB2 = new EntityB( "entity_b_2" ); + session.persist( entityB1 ); + session.persist( entityB2 ); + EntityA entityA = new EntityA( "entity_a", List.of( entityB1, entityB2 ) ); + session.persist( entityA ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from EntityB" ).executeUpdate(); + session.createMutationQuery( "delete from EntityA" ).executeUpdate(); + } ); + } + + @Test + public void testClearChildren(SessionFactoryScope scope) { + scope.inTransaction( session -> { + EntityA entityA = session.find( EntityA.class, 1L ); + entityA.getChildren().clear(); + } ); + scope.inTransaction( session -> { + EntityA entityA = session.find( EntityA.class, 1L ); + assertEquals( 0, entityA.getChildren().size() ); + assertEquals( 0, session.createQuery( "from EntityB", EntityB.class ).getResultList().size() ); + } ); + } + + @Entity(name = "EntityA") + public static class EntityA { + @Id + @GeneratedValue + private Long id; + + private String name; + + @OneToMany(orphanRemoval = true) + // JoinColumn is needed to trigger the update on the entity table + // which by default has an update Expectation of 1 row + @JoinColumn(name = "entitya_id") + private List children; + + public EntityA() { + } + + public EntityA(String name, List children) { + this.name = name; + this.children = children; + } + + public List getChildren() { + return children; + } + } + + @Entity(name = "EntityB") + public static class EntityB { + @Id + @GeneratedValue + private Long id; + + private String name; + + public EntityB() { + } + + public EntityB(String name) { + this.name = name; + } + } +} From a66d1a5ec309d0c68b3c4e5598771fccefd76185 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 2 Feb 2023 14:08:04 +0100 Subject: [PATCH 0051/1497] HHH-16112 No expectation for one-shot collection delete using update --- .../collection/OneToManyPersister.java | 4 +++- .../model/internal/TableUpdateStandard.java | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java index ef0a757af1d5..9fd03751b38e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java @@ -25,6 +25,7 @@ import org.hibernate.internal.FilterAliasGenerator; import org.hibernate.internal.util.NullnessHelper; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.jdbc.Expectations; import org.hibernate.mapping.Collection; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; @@ -390,7 +391,8 @@ public RestrictedTableMutation generateDeleteAllAst(Mutat keyRestrictionBindings, null, parameterBinders, - sqlWhereString + sqlWhereString, + Expectations.NONE ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/internal/TableUpdateStandard.java b/hibernate-core/src/main/java/org/hibernate/sql/model/internal/TableUpdateStandard.java index c6c788c5374e..1e7063dc8c04 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/internal/TableUpdateStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/internal/TableUpdateStandard.java @@ -8,6 +8,7 @@ import java.util.List; +import org.hibernate.jdbc.Expectation; import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.model.MutationTarget; import org.hibernate.sql.model.ast.AbstractTableUpdate; @@ -22,6 +23,8 @@ public class TableUpdateStandard extends AbstractTableUpdate { private final String whereFragment; + private final Expectation expectation; + public TableUpdateStandard( MutatingTableReference mutatingTable, MutationTarget mutationTarget, @@ -31,6 +34,7 @@ public TableUpdateStandard( List optLockRestrictionBindings) { super( mutatingTable, mutationTarget, sqlComment, valueBindings, keyRestrictionBindings, optLockRestrictionBindings ); this.whereFragment = null; + this.expectation = null; } public TableUpdateStandard( @@ -41,7 +45,7 @@ public TableUpdateStandard( List keyRestrictionBindings, List optLockRestrictionBindings, List parameters) { - this( tableReference, mutationTarget, sqlComment, valueBindings, keyRestrictionBindings, optLockRestrictionBindings, parameters, null ); + this( tableReference, mutationTarget, sqlComment, valueBindings, keyRestrictionBindings, optLockRestrictionBindings, parameters, null, null ); } public TableUpdateStandard( @@ -52,9 +56,11 @@ public TableUpdateStandard( List keyRestrictionBindings, List optLockRestrictionBindings, List parameters, - String whereFragment) { + String whereFragment, + Expectation expectation) { super( tableReference, mutationTarget, sqlComment, valueBindings, keyRestrictionBindings, optLockRestrictionBindings, parameters ); this.whereFragment = whereFragment; + this.expectation = expectation; } @Override @@ -75,4 +81,12 @@ public String getWhereFragment() { public void accept(SqlAstWalker walker) { walker.visitStandardTableUpdate( this ); } + + @Override + public Expectation getExpectation() { + if ( expectation != null ) { + return expectation; + } + return super.getExpectation(); + } } From 7a55e332ed02af0c64f37d6b5636913be1644686 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 6 Feb 2023 18:00:01 +0100 Subject: [PATCH 0052/1497] HHH-16143 Documentation for composite aggregates --- .../chapters/domain/embeddables.adoc | 170 ++++++++++++++++++ .../embeddable/JsonEmbeddableTest.java | 2 +- .../embeddable/StructEmbeddableTest.java | 6 +- 3 files changed, 174 insertions(+), 4 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc index 2c5b1f1e70e4..5702eac7337c 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/embeddables.adoc @@ -442,3 +442,173 @@ include::{extrasdir}/embeddable/embeddable-multiple-namingstrategy-entity-mappin ---- You could even develop your own naming strategy to do other types of implicit naming strategies. + +[[embeddable-mapping-aggregate]] +==== Aggregate embeddable mapping + +An embeddable mapping is usually just a way to encapsulate columns of a table into a Java type, +but as of Hibernate 6.2, it is also possible to map embeddable types as SQL aggregate types. + +Currently, there are three possible SQL aggregate types which can be specified by annotating one of the following +annotations on a persistent attribute: + +* `@Struct` - maps to a named SQL object type +* `@JdbcTypeCode(SqlTypes.JSON)` - maps to the SQL type JSON +* `@JdbcTypeCode(SqlTypes.SQLXML)` - maps to the SQL type XML + +Any read or assignment (in an update statement) expression for an attribute of such an embeddable +will resolve to the proper SQL expression to access/update the attribute of the SQL type. + +Since object, JSON and XML types are not supported equally on all databases, beware that not every mapping will work on all databases. +The following table outlines the current support for the different aggregate types: + +|=== +|Database |Struct |JSON |XML + +|PostgreSQL +|Yes +|Yes +|No (not yet) + +|Oracle +|Yes +|Yes +|No (not yet) + +|DB2 +|Yes +|No (not yet) +|No (not yet) + +|SQL Server +|No (not yet) +|No (not yet) +|No (not yet) +|=== + +Also note that embeddable types that are used in aggregate mappings do not yet support all kinds of attribute mappings, most notably: + +* Association mappings (`@ManyToOne`, `@OneToOne`, `@OneToMany`, `@ManyToMany`, `@ElementCollection`) +* Basic array mappings + +===== `@Struct` aggregate embeddable mapping + +The `@Struct` annotation can be placed on either the persistent attribute, or the embeddable type, +and requires the specification of a name i.e. the name of the SQL object type that it maps to. + +The following example mapping, maps the `EmbeddableAggregate` type to the SQL object type `structType`: + +.Mapping embeddable as SQL object type on persistent attribute level +==== +[source,java] +---- +include::{example-dir-emeddable}/StructEmbeddableTest.java[tag=embeddable-struct-type-mapping-example, indent=0] +---- +==== + +The schema generation will by default emit DDL for that object type, which looks something along the lines of + +==== +[source,sql] +---- +create type structType as ( + ... +) +create table StructHolder as ( + id bigint not null primary key, + aggregate structType +) +---- +==== + +The name and the nullability of the column can be refined through applying a `@Column` on the persistent attribute. + +One very important thing to note is that the order of columns in the DDL definition of a type must match the order that Hibernate expects. +By default, the order of columns is based on the alphabetical ordering of the embeddable type attribute names. + +Consider the following class: + +==== +[source,java] +---- +@Embeddable +@Struct(name = "myStruct") +public class MyStruct { + @Column(name = "b") + String attr1; + @Column(name = "a") + String attr2; +} +---- +==== + +The expected ordering of columns will be `(b,a)`, because the name `attr1` comes before `attr2` in alphabetical ordering. +This example aims at showing the importance of the persistent attribute name. + +Defining the embeddable type as Java record instead of a class can force a particular ordering through the definition of canonical constructor. + +==== +[source,java] +---- +@Embeddable +@Struct(name = "myStruct") +public record MyStruct ( + @Column(name = "a") + String attr2, + @Column(name = "b") + String attr1 +) {} +---- +==== + +In this particular example, the expected ordering of columns will be `(a,b)`, because the canonical constructor of the record +defines a specific ordering of persistent attributes, which Hibernate makes use of for `@Struct` mappings. + +It is not necessary to switch to Java records to configure the order though. +The `@Struct` annotation allows specifying the order through the `attributes` member, +an array of attribute names that the embeddable type declares, which defines the order in columns appear in the SQL object type. + +The same ordering as with the Java record can be achieved this way: + +==== +[source,java] +---- +@Embeddable +@Struct(name = "myStruct", attributes = {"attr2", "attr1"}) +public class MyStruct { + @Column(name = "b") + String attr1; + @Column(name = "a") + String attr2; +} +---- +==== + +===== JSON/XML aggregate embeddable mapping + +The `@JdbcTypeCode` annotation for JSON and XML mappings can only be placed on the persistent attribute. + +The following example mapping, maps the `EmbeddableAggregate` type to the JSON SQL type: + +.Mapping embeddable as JSON +==== +[source,java] +---- +include::{example-dir-emeddable}/JsonEmbeddableTest.java[tag=embeddable-json-type-mapping-example, indent=0] +---- +==== + +The schema generation will by default emit DDL that ensures the constraints of the embeddable type are respected, which looks something along the lines of + +==== +[source,sql] +---- +create table JsonHolder as ( + id bigint not null primary key, + aggregate json, + check (json_value(aggregate, '$.attribute1') is not null) +) +---- +==== + +Again, the name and the nullability of the `aggregate` column can be refined through applying a `@Column` on the persistent attribute. diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/JsonEmbeddableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/JsonEmbeddableTest.java index 04ba68dd6ee3..ed76d715d94e 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/JsonEmbeddableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/JsonEmbeddableTest.java @@ -302,8 +302,8 @@ public static class JsonHolder { @JdbcTypeCode(SqlTypes.JSON) private EmbeddableAggregate aggregate; + //end::embeddable-json-type-mapping-example[] //Getters and setters are omitted for brevity - //end::embeddable-json-type-mapping-example[] public JsonHolder() { } diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/StructEmbeddableTest.java b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/StructEmbeddableTest.java index cfa271151513..dab158c1ad34 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/StructEmbeddableTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/embeddable/StructEmbeddableTest.java @@ -555,13 +555,11 @@ public static class StructHolder { @Id private Long id; - //end::embeddable-struct-type-mapping-example[] @Struct(name = "structType") - //tag::embeddable-struct-type-mapping-example[] private EmbeddableAggregate aggregate; + //end::embeddable-struct-type-mapping-example[] //Getters and setters are omitted for brevity - //end::embeddable-struct-type-mapping-example[] public StructHolder() { } @@ -587,5 +585,7 @@ public void setAggregate(EmbeddableAggregate aggregate) { this.aggregate = aggregate; } + //tag::embeddable-struct-type-mapping-example[] } + //end::embeddable-struct-type-mapping-example[] } From a25fa21b0c77826f2afccdbcc4ca35fe62ba9d9b Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Fri, 3 Feb 2023 17:24:04 +0100 Subject: [PATCH 0053/1497] HHH-16123 Add test for issue --- .../criteria/TreatAbstractSuperclassTest.java | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatAbstractSuperclassTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatAbstractSuperclassTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatAbstractSuperclassTest.java new file mode 100644 index 000000000000..9ce907a509f1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatAbstractSuperclassTest.java @@ -0,0 +1,192 @@ + +/* + * 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.orm.test.jpa.criteria; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +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 jakarta.persistence.OneToMany; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + TreatAbstractSuperclassTest.LongBook.class, + TreatAbstractSuperclassTest.ShortBook.class, + TreatAbstractSuperclassTest.Author.class, + TreatAbstractSuperclassTest.AuthorParticipation.class, +}) +public class TreatAbstractSuperclassTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Author author = new Author( "Frank Herbert" ); + final ShortBook book = new ShortBook( "Dune" ); + final AuthorParticipation participation = new AuthorParticipation( author, book ); + book.getParticipations().add( participation ); + session.persist( author ); + session.persist( book ); + session.persist( participation ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from AuthorParticipation" ).executeUpdate(); + session.createMutationQuery( "delete from Author" ).executeUpdate(); + session.createMutationQuery( "delete from Publication" ).executeUpdate(); + } ); + } + + @Test + public void testJoin(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery criteria = cb.createQuery( String.class ); + final Root publicationRoot = criteria.from( Publication.class ); + // Treat as nested abstract superclass + final Root bookRoot = cb.treat( publicationRoot, Book.class ); + final Join join = bookRoot.join( "participations" ); + criteria.select( bookRoot.get( "title" ) ).where( cb.equal( + join.get( "author" ).get( "name" ), + "Frank Herbert" + ) ); + assertEquals( "Dune", session.createQuery( criteria ).getSingleResult() ); + } ); + } + + @Test + public void testSubclassJoin(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery criteria = cb.createQuery( String.class ); + final Root publicationRoot = criteria.from( Publication.class ); + // Treat as child of nested abstract superclass + final Root shortBookRoot = cb.treat( publicationRoot, ShortBook.class ); + final Join join = shortBookRoot.join( "participations" ); + criteria.select( shortBookRoot.get( "title" ) ).where( cb.equal( + join.get( "author" ).get( "name" ), + "Frank Herbert" + ) ); + assertEquals( "Dune", session.createQuery( criteria ).getSingleResult() ); + } ); + } + + @MappedSuperclass + public static class BaseEntity { + @Id + @GeneratedValue + private long id; + } + + @Entity(name = "Publication") + @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) + public abstract static class Publication extends BaseEntity { + private String title; + + public Publication() { + } + + public Publication(String title) { + this.title = title; + } + } + + @Entity(name = "Book") + public static abstract class Book extends Publication { + @OneToMany(mappedBy = "book", cascade = CascadeType.REMOVE) + private List participations = new ArrayList<>(); + + public Book() { + } + + public Book(String title) { + super( title ); + } + + public List getParticipations() { + return participations; + } + } + + @Entity(name = "LongBook") + public static class LongBook extends Book { + public LongBook() { + } + + public LongBook(String title) { + super( title ); + } + } + + @Entity(name = "ShortBook") + public static class ShortBook extends Book { + public ShortBook() { + } + + public ShortBook(String title) { + super( title ); + } + } + + @Entity(name = "Author") + public static class Author extends BaseEntity { + private String name; + + public Author() { + } + + public Author(String name) { + this.name = name; + } + } + + @Entity(name = "AuthorParticipation") + public static class AuthorParticipation extends BaseEntity { + @ManyToOne + @JoinColumn(name = "author_id", nullable = false) + private Author author; + + @ManyToOne + @JoinColumn(name = "book_id", nullable = false) + private Book book; + + public AuthorParticipation() { + } + + public AuthorParticipation(Author author, Book book) { + this.author = author; + this.book = book; + } + } +} From e8cba53020e1760f4e13f9376f75e01e6fcb3176 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 9 Feb 2023 12:56:46 +0100 Subject: [PATCH 0054/1497] HHH-16123 Add another test and fix rendering the pruned subquery in UnionSubclassEntityPersister --- .../entity/UnionSubclassEntityPersister.java | 54 +++++++++++++------ .../criteria/TreatAbstractSuperclassTest.java | 45 ++++++++++++++++ 2 files changed, 82 insertions(+), 17 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java index a5acf495c233..438ca00054c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java @@ -8,6 +8,7 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -39,6 +40,7 @@ import org.hibernate.mapping.Subclass; import org.hibernate.mapping.Table; import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.TableDetails; @@ -528,26 +530,16 @@ protected String generateSubquery(Set treated) { // Collect all selectables of every entity subtype and group by selection expression as well as table name final LinkedHashMap> selectables = new LinkedHashMap<>(); - final SelectableConsumer selectableConsumer = (i, selectable) -> - selectables.computeIfAbsent( selectable.getSelectionExpression(), k -> new HashMap<>() ) - .put( selectable.getContainingTableExpression(), selectable ); // Collect the concrete subclass table names for the treated entity names final Set treatedTableNames = new HashSet<>( treated.size() ); for ( String subclassName : treated ) { final UnionSubclassEntityPersister subPersister = (UnionSubclassEntityPersister) metamodel.getEntityDescriptor( subclassName ); - for ( String subclassTableName : subPersister.getSubclassTableNames() ) { - if ( ArrayHelper.indexOf( subclassSpaces, subclassTableName ) != -1 ) { - treatedTableNames.add( subclassTableName ); - } - } - subPersister.getIdentifierMapping().forEachSelectable( selectableConsumer ); - if ( subPersister.getVersionMapping() != null ) { - subPersister.getVersionMapping().forEachSelectable( selectableConsumer ); - } - subPersister.visitSubTypeAttributeMappings( - attributeMapping -> attributeMapping.forEachSelectable( selectableConsumer ) - ); + // Collect all the real (non-abstract) table names + treatedTableNames.addAll( Arrays.asList( subPersister.getConstraintOrderedTableNameClosure() ) ); + // Collect selectables grouped by the table names in which they appear + // TODO: we could cache this + subPersister.collectSelectableOwners( selectables ); } // Create a union sub-query for the table names, like generateSubquery(PersistentClass model, Mapping mapping) @@ -555,8 +547,8 @@ protected String generateSubquery(Set treated) { .append( "( " ); final StringBuilderSqlAppender sqlAppender = new StringBuilderSqlAppender( buf ); - for ( String name : getSubclassEntityNames() ) { - final AbstractEntityPersister persister = (AbstractEntityPersister) metamodel.findEntityDescriptor( name ); + for ( EntityMappingType mappingType : getSubMappingTypes() ) { + final AbstractEntityPersister persister = (AbstractEntityPersister) mappingType; final String subclassTableName = persister.getTableName(); if ( treatedTableNames.contains( subclassTableName ) ) { if ( buf.length() > 2 ) { @@ -587,6 +579,34 @@ protected String generateSubquery(Set treated) { return buf.append( " )" ).toString(); } + private void collectSelectableOwners(LinkedHashMap> selectables) { + if ( isAbstract() ) { + for ( EntityMappingType subMappingType : getSubMappingTypes() ) { + if ( !subMappingType.isAbstract() ) { + ( (UnionSubclassEntityPersister) subMappingType ).collectSelectableOwners( selectables ); + } + } + } + else { + final SelectableConsumer selectableConsumer = (i, selectable) -> { + Map selectableMapping = selectables.computeIfAbsent( + selectable.getSelectionExpression(), + k -> new HashMap<>() + ); + selectableMapping.put( getTableName(), selectable ); + }; + getIdentifierMapping().forEachSelectable( selectableConsumer ); + if ( getVersionMapping() != null ) { + getVersionMapping().forEachSelectable( selectableConsumer ); + } + final AttributeMappingsList attributeMappings = getAttributeMappings(); + final int size = attributeMappings.size(); + for ( int i = 0; i < size; i++ ) { + attributeMappings.get( i ).forEachSelectable( selectableConsumer ); + } + } + } + @Override protected String[] getSubclassTableKeyColumns(int j) { if ( j != 0 ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatAbstractSuperclassTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatAbstractSuperclassTest.java index 9ce907a509f1..d1f69fbf4f85 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatAbstractSuperclassTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/TreatAbstractSuperclassTest.java @@ -28,12 +28,14 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.MappedSuperclass; import jakarta.persistence.OneToMany; +import jakarta.persistence.Tuple; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.Join; import jakarta.persistence.criteria.Root; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; /** * @author Marco Belladelli @@ -42,6 +44,7 @@ @DomainModel(annotatedClasses = { TreatAbstractSuperclassTest.LongBook.class, TreatAbstractSuperclassTest.ShortBook.class, + TreatAbstractSuperclassTest.Article.class, TreatAbstractSuperclassTest.Author.class, TreatAbstractSuperclassTest.AuthorParticipation.class, }) @@ -85,6 +88,25 @@ public void testJoin(SessionFactoryScope scope) { } ); } + @Test + public void testTreatMultiple(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery criteria = cb.createTupleQuery(); + final Root publicationRoot = criteria.from( Publication.class ); + // Treat as nested abstract superclass + final Root bookRoot = cb.treat( publicationRoot, Book.class ); + final Root
articleRoot = cb.treat( publicationRoot, Article.class ); + criteria.multiselect( + bookRoot.get( "title" ), + articleRoot.get( "reference" ) + ); + final Tuple tuple = session.createQuery( criteria ).getSingleResult(); + assertEquals( "Dune", tuple.get( 0 ) ); + assertNull( tuple.get( 1 ) ); + } ); + } + @Test public void testSubclassJoin(SessionFactoryScope scope) { scope.inTransaction( session -> { @@ -122,8 +144,29 @@ public Publication(String title) { } } + @Entity(name = "Article") + public static class Article extends Publication { + private String reference; + + public Article() { + } + + public Article(String title) { + super( title ); + } + + public String getReference() { + return reference; + } + + public void setReference(String reference) { + this.reference = reference; + } + } + @Entity(name = "Book") public static abstract class Book extends Publication { + private String isbn; @OneToMany(mappedBy = "book", cascade = CascadeType.REMOVE) private List participations = new ArrayList<>(); @@ -141,6 +184,7 @@ public List getParticipations() { @Entity(name = "LongBook") public static class LongBook extends Book { + private int pageCount; public LongBook() { } @@ -151,6 +195,7 @@ public LongBook(String title) { @Entity(name = "ShortBook") public static class ShortBook extends Book { + private int readTime; public ShortBook() { } From d6967ea2cbf02fcd3a0eb219e20aca34e5d65b1f Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 9 Feb 2023 11:51:37 +0100 Subject: [PATCH 0055/1497] HHH-15829 Add test for issue --- .../OneToManyEagerDiscriminatorTest.java | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyEagerDiscriminatorTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyEagerDiscriminatorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyEagerDiscriminatorTest.java new file mode 100644 index 000000000000..cfb8d21b1adb --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyEagerDiscriminatorTest.java @@ -0,0 +1,148 @@ +/* + * 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.orm.test.inheritance.discriminator.associations; + +import java.io.Serializable; +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + OneToManyEagerDiscriminatorTest.User.class, + OneToManyEagerDiscriminatorTest.ValueBase.class, + OneToManyEagerDiscriminatorTest.UserValueBase.class +}) +@JiraKey("HHH-15829") +public class OneToManyEagerDiscriminatorTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final User user = new User(); + final UserValueBase value = new UserValueBase(); + value.setData( "value_1" ); + value.setEntity( user ); + session.persist( user ); + session.persist( value ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from ValueBase" ).executeUpdate() ); + scope.inTransaction( session -> session.createMutationQuery( "delete from User" ).executeUpdate() ); + } + + @Test + public void test(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final User user = session.find( User.class, 1L ); + assertNotNull( user ); + assertTrue( Hibernate.isInitialized( user.getProperties() ) ); + assertEquals( 1, user.getProperties().size() ); + assertEquals( "value_1", user.getProperties().iterator().next().getData() ); + } ); + } + + @Entity(name = "User") + @Table(name = "Users") + public static class User implements Serializable { + @Id + @GeneratedValue + private Long id; + + @OneToMany(mappedBy = "entity", fetch = FetchType.EAGER) + private Set properties = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Set getProperties() { + return properties; + } + + public void setProperties(Set properties) { + this.properties = properties; + } + } + + @Entity(name = "ValueBase") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "target_type", discriminatorType = DiscriminatorType.INTEGER) + public abstract static class ValueBase implements Serializable { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + public Long getId() { + return id; + } + } + + @Entity(name = "UserValueBase") + @DiscriminatorValue("1") + public static class UserValueBase extends ValueBase { + private String data; + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "entity_id", nullable = false) + private User entity; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public User getEntity() { + return entity; + } + + public void setEntity(User entity) { + this.entity = entity; + } + } +} From 2b3ce400b3c01219c51b9f5e25063568ba3d0653 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 9 Feb 2023 15:13:13 +0100 Subject: [PATCH 0056/1497] HHH-16037 HHH-16053 Add test for issues --- .../NestedOneToOneDiscriminatorTest.java | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/NestedOneToOneDiscriminatorTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/NestedOneToOneDiscriminatorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/NestedOneToOneDiscriminatorTest.java new file mode 100644 index 000000000000..99d30b620ec9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/NestedOneToOneDiscriminatorTest.java @@ -0,0 +1,182 @@ +/* + * 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.orm.test.inheritance.discriminator.associations; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + NestedOneToOneDiscriminatorTest.Person.class, + NestedOneToOneDiscriminatorTest.BodyPart.class, + NestedOneToOneDiscriminatorTest.Leg.class, + NestedOneToOneDiscriminatorTest.NestedEntity.class +}) +public class NestedOneToOneDiscriminatorTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Person person = new Person(); + person.setName( "initialName" ); + person.getLegs().add( new Leg( "left leg", person, new NestedEntity( "nested in left leg" ) ) ); + session.persist( person ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from BodyPart" ).executeUpdate(); + session.createMutationQuery( "delete from Person" ).executeUpdate(); + session.createMutationQuery( "delete from NestedEntity" ).executeUpdate(); + } ); + } + + @Test + @JiraKey("HHH-16037") + public void testUpdatePerson(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Person person = session.find( Person.class, 1L ); + assertEquals( "initialName", person.getName() ); + assertEquals( 1L, person.getId() ); + person.setName( "changedName" ); + } ); + scope.inTransaction( session -> { + final Person person = session.find( Person.class, 1L ); + assertEquals( "changedName", person.getName() ); + assertEquals( 1L, person.getId() ); + } ); + } + + @Test + @JiraKey("HHH-16053") + public void testJoinFetchNested(SessionFactoryScope scope) { + scope.inTransaction( session -> { + String jpql = "select person " + + "from Person person " + + "left join fetch person.legs as leg " + + "left join fetch leg.nestedEntity " + + "where person.id = :id"; + final Person person = session.createQuery( jpql, Person.class ) + .setParameter( "id", 1L ) + .getSingleResult(); + assertEquals( 1L, person.getId() ); + assertTrue( Hibernate.isInitialized( person.getLegs() ) ); + final Leg leg = person.getLegs().iterator().next(); + assertEquals( "left leg", leg.getName() ); + assertTrue( Hibernate.isInitialized( leg.getNestedEntity() ) ); + assertEquals( "nested in left leg", leg.getNestedEntity().getName() ); + } ); + } + + @MappedSuperclass + public abstract static class BaseEntity { + @Id + @GeneratedValue + private Long id; + + protected String name; + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Person") + @Table(name = "person") + public static class Person extends BaseEntity { + @OneToMany(fetch = FetchType.EAGER, mappedBy = "person", cascade = CascadeType.ALL) + private Set legs = new HashSet<>(); + + public Set getLegs() { + return legs; + } + } + + @Entity(name = "BodyPart") + @Table(name = "body_part") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "discriminator") + public abstract static class BodyPart extends BaseEntity { + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + protected NestedEntity nestedEntity; + + public NestedEntity getNestedEntity() { + return nestedEntity; + } + } + + @Entity(name = "Leg") + @DiscriminatorValue("LegBodyPart") + public static class Leg extends BodyPart { + @ManyToOne(fetch = FetchType.EAGER, optional = false) + private Person person; + + public Leg() { + } + + public Leg(String name, Person person, NestedEntity nestedEntity) { + this.name = name; + this.person = person; + this.nestedEntity = nestedEntity; + } + + public Person getPerson() { + return person; + } + } + + @Entity(name = "NestedEntity") + @Table(name = "nested_entity") + public static class NestedEntity extends BaseEntity { + public NestedEntity() { + } + + public NestedEntity(String name) { + this.name = name; + } + } +} From 1b89defbb6b2f228e8f1f4614fa067cf778beed4 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 9 Feb 2023 16:12:35 +0100 Subject: [PATCH 0057/1497] HHH-16157 Add test for issue and fix duplicate discriminator conditions in join fetch queries --- .../sqm/sql/BaseSqmToSqlAstConverter.java | 42 +++--- .../OneToManyJoinFetchDiscriminatorTest.java | 139 ++++++++++++++++++ 2 files changed, 157 insertions(+), 24 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyJoinFetchDiscriminatorTest.java 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 bff7b2130a4f..1c052c3811ba 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 @@ -3084,18 +3084,7 @@ private TableGroup consumeAttributeJoin( joinedTableGroup = joinedTableGroupJoin.getJoinedGroup(); pluralAttributeMapping.applyBaseRestrictions( - (predicate) -> { - final PredicateCollector existing = collectionFilterPredicates.get( joinedTableGroup.getNavigablePath() ); - final PredicateCollector collector; - if ( existing == null ) { - collector = new PredicateCollector( predicate ); - collectionFilterPredicates.put( joinedTableGroup.getNavigablePath(), collector ); - } - else { - collector = existing; - collector.applyPredicate( predicate ); - } - }, + (predicate) -> addCollectionFilterPredicate( joinedTableGroup.getNavigablePath(), predicate ), joinedTableGroup, true, getLoadQueryInfluencers().getEnabledFilters(), @@ -7328,6 +7317,7 @@ else if ( getLoadQueryInfluencers().hasEnabledFetchProfiles() ) { fetchable, fetchTiming, joined, + explicitFetch, alias ); @@ -7425,6 +7415,7 @@ private Fetch buildFetch( Fetchable fetchable, FetchTiming fetchTiming, boolean joined, + boolean explicitFetch, String alias) { // fetch has access to its parent in addition to the parent having its fetches. // @@ -7448,18 +7439,21 @@ private Fetch buildFetch( final TableGroup tableGroup = getFromClauseIndex().getTableGroup( fetchablePath ); final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; - final Joinable joinable = pluralAttributeMapping - .getCollectionDescriptor() - .getCollectionType() - .getAssociatedJoinable( getCreationContext().getSessionFactory() ); - joinable.applyBaseRestrictions( - (predicate) -> addCollectionFilterPredicate( tableGroup.getNavigablePath(), predicate ), - tableGroup, - true, - getLoadQueryInfluencers().getEnabledFilters(), - null, - this - ); + // Base restrictions have already been applied if this is an explicit fetch + if ( !explicitFetch ) { + final Joinable joinable = pluralAttributeMapping + .getCollectionDescriptor() + .getCollectionType() + .getAssociatedJoinable( getCreationContext().getSessionFactory() ); + joinable.applyBaseRestrictions( + (predicate) -> addCollectionFilterPredicate( tableGroup.getNavigablePath(), predicate ), + tableGroup, + true, + getLoadQueryInfluencers().getEnabledFilters(), + null, + this + ); + } pluralAttributeMapping.applyBaseManyToManyRestrictions( (predicate) -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyJoinFetchDiscriminatorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyJoinFetchDiscriminatorTest.java new file mode 100644 index 000000000000..565864589ac0 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyJoinFetchDiscriminatorTest.java @@ -0,0 +1,139 @@ +/* + * 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.orm.test.inheritance.discriminator.associations; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Marco Belladelli + */ +@SessionFactory(useCollectingStatementInspector = true) +@DomainModel(annotatedClasses = { + OneToManyJoinFetchDiscriminatorTest.Person.class, + OneToManyJoinFetchDiscriminatorTest.BodyPart.class, + OneToManyJoinFetchDiscriminatorTest.Leg.class +}) +@JiraKey("HHH-16157") +public class OneToManyJoinFetchDiscriminatorTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Person person = new Person(); + person.setName( "initialName" ); + person.getLegs().add( new Leg( "left leg", person ) ); + session.persist( person ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from BodyPart" ).executeUpdate(); + session.createMutationQuery( "delete from Person" ).executeUpdate(); + } ); + } + + @Test + public void testJoinFetchDiscriminatorCondition(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + statementInspector.clear(); + scope.inTransaction( session -> { + final Person person = session.createQuery( "from Person person left join fetch person.legs", Person.class ) + .getSingleResult(); + assertTrue( Hibernate.isInitialized( person.getLegs() ) ); + assertEquals( 1, person.getLegs().size() ); + statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "LegBodyPart", 1 ); + } ); + } + + @MappedSuperclass + public abstract static class BaseEntity { + @Id + @GeneratedValue + private Long id; + + protected String name; + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "Person") + @Table(name = "person") + public static class Person extends BaseEntity { + @OneToMany(fetch = FetchType.LAZY, mappedBy = "person", cascade = CascadeType.ALL) + private Set legs = new HashSet<>(); + + public Set getLegs() { + return legs; + } + } + + @Entity(name = "BodyPart") + @Table(name = "body_part") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "discriminator") + public abstract static class BodyPart extends BaseEntity { + } + + @Entity(name = "Leg") + @DiscriminatorValue("LegBodyPart") + public static class Leg extends BodyPart { + @ManyToOne(fetch = FetchType.LAZY, optional = false) + private Person person; + + public Leg() { + } + + public Leg(String name, Person person) { + this.name = name; + this.person = person; + } + + public Person getPerson() { + return person; + } + } +} From 4a3d6fbc0ea9af8aa125f3d6d857f59e6677e15a Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 10 Feb 2023 14:59:18 +0100 Subject: [PATCH 0058/1497] Remove identity generation strategy from entity in tests that doesn't need it --- .../associations/OneToManyEagerDiscriminatorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyEagerDiscriminatorTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyEagerDiscriminatorTest.java index cfb8d21b1adb..158a36e28119 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyEagerDiscriminatorTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/discriminator/associations/OneToManyEagerDiscriminatorTest.java @@ -112,7 +112,7 @@ public void setProperties(Set properties) { @DiscriminatorColumn(name = "target_type", discriminatorType = DiscriminatorType.INTEGER) public abstract static class ValueBase implements Serializable { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue private Long id; public Long getId() { From ad3cd73adaa1f9d83fa634f28dc7ed03a9ef6414 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 7 Feb 2023 21:12:38 +0100 Subject: [PATCH 0059/1497] HHH-16137 Add test for issue --- .../NamedParameterInSelectAndWhereTest.java | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/NamedParameterInSelectAndWhereTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/NamedParameterInSelectAndWhereTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/NamedParameterInSelectAndWhereTest.java new file mode 100644 index 000000000000..02d62f5017fa --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/NamedParameterInSelectAndWhereTest.java @@ -0,0 +1,66 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.query; + +import org.hibernate.testing.orm.domain.gambit.BasicEntity; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Tuple; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = BasicEntity.class) +@JiraKey("HHH-16137") +public class NamedParameterInSelectAndWhereTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + BasicEntity be1 = new BasicEntity( 1, "one" ); + session.persist( be1 ); + BasicEntity be2 = new BasicEntity( 2, "two" ); + session.persist( be2 ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from BasicEntity" ).executeUpdate() ); + } + + @Test + public void testSelectAndWhere(SessionFactoryScope scope) { + scope.inTransaction( session -> assertEquals( + 1, + session.createQuery( "SELECT :param FROM BasicEntity be WHERE be.id > :param", Integer.class ) + .setParameter( "param", 1 ) + .getSingleResult() + ) ); + } + + @Test + public void testSelectAndWhereIsNull(SessionFactoryScope scope) { + scope.inTransaction( session -> assertEquals( + 1, + session.createQuery( + "SELECT :param FROM BasicEntity be WHERE :param is null or be.id > :param", + Integer.class + ) + .setParameter( "param", 1 ) + .getSingleResult() + ) ); + } +} From 0e9e631e3edd8c52ad1c3682c0463e8947c3a80d Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 7 Feb 2023 21:41:01 +0100 Subject: [PATCH 0060/1497] HHH-16137 Fix check when replacing jdbc parameter --- .../org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1c052c3811ba..e8fcb408870d 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 @@ -5210,7 +5210,7 @@ private void replaceJdbcParametersType( for ( List parameters : jdbcParamsForSqmParameter ) { assert parameters.size() == 1; final JdbcParameter jdbcParameter = parameters.get( 0 ); - if ( ( (SqlExpressible) jdbcParameter ).getJdbcMapping() != valueMapping ) { + if ( ( (SqlExpressible) jdbcParameter ).getJdbcMapping() != jdbcMapping ) { final JdbcParameter newJdbcParameter = new JdbcParameterImpl( jdbcMapping ); parameters.set( 0, newJdbcParameter ); jdbcParameters.getJdbcParameters().remove( jdbcParameter ); From 24d1aa67fc9f78f98ad8955dda9dcb65f77012bf Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 10 Feb 2023 12:22:35 +0100 Subject: [PATCH 0061/1497] HHH-16165 Add test for issue --- .../manytomany/ManyToManyOrderByTest.java | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/ManyToManyOrderByTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/ManyToManyOrderByTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/ManyToManyOrderByTest.java new file mode 100644 index 000000000000..38acb46d2d4d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/manytomany/ManyToManyOrderByTest.java @@ -0,0 +1,149 @@ +package org.hibernate.orm.test.annotations.manytomany; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OrderBy; +import jakarta.persistence.Table; +import jakarta.persistence.Tuple; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + ManyToManyOrderByTest.Author.class, + ManyToManyOrderByTest.Book.class, + } +) +@SessionFactory +@TestForIssue(jiraKey = "HHH-16165") +public class ManyToManyOrderByTest { + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Author author = new Author( "Iain M Banks" ); + Book book1 = new Book( "Feersum Endjinn" ); + Book book2 = new Book( "Use of Weapons" ); + author.books.add( book1 ); + author.books.add( book2 ); + session.persist( book1 ); + session.persist( book2 ); + session.persist( author ); + } + ); + } + + @Test + public void testQueryWithDistinct(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Author author = session.createQuery( + "select distinct a from Author a left join fetch a.books", + Author.class + ) + .getSingleResult(); + assertThat( author ).isNotNull(); + } + ); + } + + @Test + public void testQueryWithDistinct2(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + List results = session.createQuery( + "select distinct a, 1 from Author a left join fetch a.books", + Tuple.class + ) + .list(); + for ( Tuple t : results ) { + assertThat( t.getElements().size() ).isEqualTo( 2 ); + assertThat( t.get( 0 ) ).isInstanceOf( Author.class ); + assertThat( t.get( 1 ) ).isEqualTo( 1 ); + } + } + ); + } + + @Test + public void testQuery(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Author author = session.createQuery( + "select a from Author a left join fetch a.books", + Author.class + ) + .getSingleResult(); + assertThat( author ).isNotNull(); + } + ); + } + + @Entity(name = "Book") + @Table(name = "MTMBook") + static class Book { + + @GeneratedValue + @Id + long id; + + @Basic(optional = false) + String title; + + Book(String title) { + this.title = title; + } + + Book() { + } + } + + @Entity(name = "Author") + @Table(name = "MTMAuthor") + static class Author { + + @GeneratedValue + @Id + long id; + + @Basic(optional = false) + String name; + + @ManyToMany + @OrderBy("id") + List books = new ArrayList<>(); + + Author(String name) { + this.name = name; + } + + public Author() { + } + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public List getBooks() { + return books; + } + } +} From 91e3a381224cb119db24c954198d39e5bcf40376 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 10 Feb 2023 12:23:11 +0100 Subject: [PATCH 0062/1497] HHH-16165 Incorrect SQL generated when using SELECT DISTINCT and @OrderBy --- .../mapping/internal/AbstractDomainPath.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) 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 1fed0f282126..31753c48529f 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 @@ -19,6 +19,7 @@ import org.hibernate.query.sqm.NullPrecedence; import org.hibernate.query.sqm.SortOrder; import org.hibernate.sql.ast.spi.SqlAstCreationState; +import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; @@ -26,8 +27,10 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectClause; import org.hibernate.sql.ast.tree.select.SortSpecification; import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.internal.SqlSelectionImpl; import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey; @@ -258,6 +261,19 @@ private void addSortSpecification( } } } + + final SelectClause selectClause = ast.getSelectClause(); + + if ( selectClause.isDistinct() && selectClauseDoesNotContainOrderExpression( expression, selectClause ) ) { + final int valuesArrayPosition = selectClause.getSqlSelections().size(); + SqlSelection sqlSelection = new SqlSelectionImpl( + valuesArrayPosition + 1, + valuesArrayPosition, + expression + ); + selectClause.addSqlSelection( sqlSelection ); + } + final Expression sortExpression = OrderingExpression.applyCollation( expression, collation, @@ -265,4 +281,13 @@ private void addSortSpecification( ); ast.addSortSpecification( new SortSpecification( sortExpression, sortOrder, nullPrecedence ) ); } + + private static boolean selectClauseDoesNotContainOrderExpression(Expression expression, SelectClause selectClause) { + for ( SqlSelection sqlSelection : selectClause.getSqlSelections() ) { + if ( sqlSelection.getExpression().equals( expression ) ) { + return false; + } + } + return true; + } } From de59b447792fa1c493eb09ef7f24e6d17b15bd4f Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 10 Feb 2023 11:16:00 +0100 Subject: [PATCH 0063/1497] Get rid of Clause parameter in JdbcParameterBindings --- .../loader/ast/internal/AbstractNaturalIdLoader.java | 2 -- .../loader/ast/internal/CollectionElementLoaderByIndex.java | 3 --- .../loader/ast/internal/CollectionLoaderBatchKey.java | 2 -- .../loader/ast/internal/CollectionLoaderSingleKey.java | 3 --- .../loader/ast/internal/DatabaseSnapshotExecutor.java | 3 --- .../loader/ast/internal/MultiIdLoaderStandard.java | 3 --- .../loader/ast/internal/MultiNaturalIdLoadingBatcher.java | 3 --- .../ast/internal/SingleIdEntityLoaderDynamicBatch.java | 3 --- .../org/hibernate/loader/ast/internal/SingleIdLoadPlan.java | 3 --- .../ast/internal/SingleUniqueKeyEntityLoaderStandard.java | 3 --- .../mapping/internal/GeneratedValuesProcessor.java | 2 -- .../main/java/org/hibernate/query/sqm/internal/SqmUtil.java | 2 -- .../org/hibernate/sql/exec/spi/JdbcParameterBindings.java | 6 +----- 13 files changed, 1 insertion(+), 37 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java index 257ed62546b5..0b35b2d819fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractNaturalIdLoader.java @@ -30,7 +30,6 @@ import org.hibernate.query.internal.SimpleQueryOptions; import org.hibernate.query.spi.QueryOptions; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.SimpleFromClauseAccessImpl; import org.hibernate.sql.ast.spi.SqlAliasBaseManager; @@ -316,7 +315,6 @@ public Object resolveIdToNaturalId(Object id, SharedSessionContractImplementor s final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); int offset = jdbcParamBindings.registerParametersForEachJdbcValue( id, - Clause.WHERE, entityDescriptor().getIdentifierMapping(), jdbcParameters, session diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionElementLoaderByIndex.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionElementLoaderByIndex.java index 37ff815aa2b3..04a3eedd1334 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionElementLoaderByIndex.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionElementLoaderByIndex.java @@ -23,7 +23,6 @@ import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -116,14 +115,12 @@ public Object load(Object key, Object index, SharedSessionContractImplementor se int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( key, - Clause.WHERE, attributeMapping.getKeyDescriptor(), jdbcParameters, session ); offset += jdbcParameterBindings.registerParametersForEachJdbcValue( incrementIndexByBase( index ), - Clause.WHERE, offset, attributeMapping.getIndexDescriptor(), jdbcParameters, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderBatchKey.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderBatchKey.java index 728a691fa3f6..6554eb427c4d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderBatchKey.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderBatchKey.java @@ -25,7 +25,6 @@ import org.hibernate.loader.ast.spi.CollectionLoader; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -180,7 +179,6 @@ private void batchLoad( for ( int i = smallBatchStart; i < smallBatchStart + smallBatchLength; i++ ) { offset += jdbcParameterBindings.registerParametersForEachJdbcValue( batchIds[i], - Clause.WHERE, offset, getLoadable().getKeyDescriptor(), jdbcParameters, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java index 242b02cff93e..98e87aa55a27 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSingleKey.java @@ -22,8 +22,6 @@ import org.hibernate.loader.ast.spi.CollectionLoader; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -99,7 +97,6 @@ public PersistentCollection load(Object key, SharedSessionContractImplementor final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( keyJdbcCount ); int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( key, - Clause.WHERE, attributeMapping.getKeyDescriptor(), jdbcParameters, session diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java index ab4d6a5d80ce..acf108bfdec6 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/DatabaseSnapshotExecutor.java @@ -17,11 +17,9 @@ import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.sqm.ComparisonOperator; import org.hibernate.query.sqm.sql.FromClauseIndex; import org.hibernate.spi.NavigablePath; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.spi.SqlAliasBaseManager; import org.hibernate.sql.ast.spi.SqlExpressionResolver; @@ -165,7 +163,6 @@ Object[] loadDatabaseSnapshot(Object id, SharedSessionContractImplementor sessio int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( id, - Clause.WHERE, entityDescriptor.getIdentifierMapping(), jdbcParameters, session diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdLoaderStandard.java index e68ea701959a..061f231a947a 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdLoaderStandard.java @@ -23,7 +23,6 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.engine.spi.Status; import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEvent; @@ -35,7 +34,6 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -264,7 +262,6 @@ private List loadEntitiesById( offset += jdbcParameterBindings.registerParametersForEachJdbcValue( id, - Clause.WHERE, offset, entityDescriptor.getIdentifierMapping(), jdbcParameters, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java index 3c9f22b5b5ff..09454424d9a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java @@ -21,7 +21,6 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -102,7 +101,6 @@ public List multiLoad(Object[] naturalIdValues, MultiNaturalIdLoadOptions if ( bindValue != null ) { offset += jdbcParamBindings.registerParametersForEachJdbcValue( bindValue, - Clause.IRRELEVANT, offset, entityDescriptor.getNaturalIdMapping(), jdbcParameters, @@ -124,7 +122,6 @@ public List multiLoad(Object[] naturalIdValues, MultiNaturalIdLoadOptions // pad the remaining parameters with null offset += jdbcParamBindings.registerParametersForEachJdbcValue( null, - Clause.IRRELEVANT, offset, entityDescriptor.getNaturalIdMapping(), jdbcParameters, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java index 3b687dc1419e..43485c5bfd51 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java @@ -21,8 +21,6 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptionsAdapter; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -120,7 +118,6 @@ public T load( for ( int i = 0; i < numberOfIds; i++ ) { offset += jdbcParameterBindings.registerParametersForEachJdbcValue( idsToLoad[i], - Clause.WHERE, offset, getLoadable().getIdentifierMapping(), jdbcParameters, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java index b0333987aa10..a5c0edadfb91 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java @@ -18,8 +18,6 @@ import org.hibernate.query.internal.SimpleQueryOptions; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptionsAdapter; -import org.hibernate.query.spi.QueryParameterBindings; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -134,7 +132,6 @@ public T load( while ( offset < jdbcParameters.size() ) { offset += jdbcParameterBindings.registerParametersForEachJdbcValue( restrictedValue, - Clause.WHERE, offset, restrictivePart, jdbcParameters, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java index ae7f0e9d9a1e..2a03441e2bb0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java @@ -23,7 +23,6 @@ import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; @@ -88,7 +87,6 @@ public T load( final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( ukValue, - Clause.WHERE, uniqueKeyAttribute, jdbcParameters, session @@ -145,7 +143,6 @@ public Object resolveId(Object ukValue, SharedSessionContractImplementor session final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( ukValue, - Clause.WHERE, uniqueKeyAttribute, jdbcParameters, session diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java index b3d0e7305e34..15f0c156b18a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java @@ -22,7 +22,6 @@ import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.spi.QueryOptions; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; @@ -124,7 +123,6 @@ private JdbcParameterBindings getJdbcParameterBindings(Object id, SharedSessionC final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( jdbcParameters.size() ); int offset = jdbcParamBindings.registerParametersForEachJdbcValue( id, - Clause.WHERE, entityDescriptor.getIdentifierMapping(), jdbcParameters, session 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 0570ad1afada..c1dbdeb03133 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 @@ -48,7 +48,6 @@ import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.jpa.ParameterCollector; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlTreeCreationException; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -389,7 +388,6 @@ else if ( parameterType instanceof EntityAssociationMapping ) { int offset = jdbcParameterBindings.registerParametersForEachJdbcValue( bindValue, - Clause.IRRELEVANT, parameterType, jdbcParams, session diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java index 2a4a6c4f6ffc..deabfafacc06 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java @@ -12,7 +12,6 @@ import java.util.function.BiConsumer; import org.hibernate.dialect.Dialect; -import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; @@ -25,7 +24,6 @@ import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sql.internal.NativeQueryImpl; import org.hibernate.query.sql.spi.ParameterOccurrence; -import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterImpl; @@ -75,16 +73,14 @@ public void visitBindings(BiConsumer action default int registerParametersForEachJdbcValue( Object value, - Clause clause, Bindable bindable, List jdbcParameters, SharedSessionContractImplementor session) { - return registerParametersForEachJdbcValue( value, clause, 0, bindable, jdbcParameters, session ); + return registerParametersForEachJdbcValue( value, 0, bindable, jdbcParameters, session ); } default int registerParametersForEachJdbcValue( Object value, - Clause clause, int offset, Bindable bindable, List jdbcParameters, From 15b24d6c1428dfc587dcf9b0afd581902cbde0a1 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 9 Feb 2023 13:21:11 -0600 Subject: [PATCH 0064/1497] HHH-16148 - Introduce Immutability (MutabilityPlan) for use with @Mutability HHH-16141 - Support @Mutability and @Immutable on UserType HHH-16147 - Support @Mutability and @Immutable on AttributeConverter HHH-16146 - Improve User Guide documentation for (im)mutability --- ...ection-immutability-update-example.log.txt | 4 +- .../chapters/domain/immutability.adoc | 197 +++++++++++-- .../immutability/EntityImmutabilityTest.java | 100 ------- .../org/hibernate/annotations/Mutability.java | 15 +- .../boot/model/internal/BasicValueBinder.java | 150 ++++++---- .../boot/model/internal/EntityBinder.java | 54 ++-- .../internal/InferredBasicValueResolver.java | 40 ++- .../org/hibernate/mapping/BasicValue.java | 1 + .../internal/MappingModelCreationHelper.java | 2 +- .../type/descriptor/java/Immutability.java | 52 ++++ .../java/ImmutableMutabilityPlan.java | 5 + .../mutabiity/ConvertedMapImmutableTests.java | 165 ----------- .../mutabiity/ConvertedMutabilityTests.java | 188 ------------- .../mutability/MutabilityBaselineEntity.java | 98 +++++++ .../BasicAttributeMutabilityTests.java | 260 ++++++++++++++++++ .../EntityAttributeMutabilityTest.java | 184 +++++++++++++ .../ImmutabilityMapAsBasicTests.java | 193 +++++++++++++ .../attribute/ImmutableMapAsBasicTests.java | 210 ++++++++++++++ .../attribute/MutableMapAsBasicTests.java} | 85 +----- .../PluralAttributeMutabilityTest.java | 67 +++-- .../mutability/converted/DateConverter.java | 38 +++ .../converted/ImmutabilityConverterTests.java | 85 ++++++ .../converted/ImmutabilityDateConverter.java | 17 ++ .../converted/ImmutabilityMapConverter.java | 17 ++ .../ImmutableConvertedBaselineTests.java | 226 +++++++++++++++ .../converted/ImmutableConverterTests.java | 100 +++++++ .../converted/ImmutableDateConverter.java | 16 ++ .../converted/ImmutableMapConverter.java | 16 ++ .../mutability/converted/MapConverter.java | 35 +++ .../entity/EntityImmutabilityTest.java | 113 ++++++++ .../entity/EntityMutabilityPlanTest.java | 50 ++++ .../mutability/entity/package-info.java | 13 + .../testing/orm/junit/DomainModelScope.java | 5 + .../orm/junit/SessionFactoryScope.java | 4 + 34 files changed, 2144 insertions(+), 661 deletions(-) delete mode 100644 documentation/src/test/java/org/hibernate/userguide/immutability/EntityImmutabilityTest.java create mode 100644 hibernate-core/src/main/java/org/hibernate/type/descriptor/java/Immutability.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapImmutableTests.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMutabilityTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/MutabilityBaselineEntity.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/BasicAttributeMutabilityTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/EntityAttributeMutabilityTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/ImmutabilityMapAsBasicTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/ImmutableMapAsBasicTests.java rename hibernate-core/src/test/java/org/hibernate/orm/test/mapping/{converted/converter/mutabiity/ConvertedMapMutableTests.java => mutability/attribute/MutableMapAsBasicTests.java} (57%) rename documentation/src/test/java/org/hibernate/userguide/immutability/CollectionImmutabilityTest.java => hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/PluralAttributeMutabilityTest.java (66%) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/DateConverter.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityConverterTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityDateConverter.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityMapConverter.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableConvertedBaselineTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableConverterTests.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableDateConverter.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableMapConverter.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/MapConverter.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/EntityImmutabilityTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/EntityMutabilityPlanTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/package-info.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/immutability/collection-immutability-update-example.log.txt b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/immutability/collection-immutability-update-example.log.txt index 8fcb37705d20..2d7e3b3ad41f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/extras/immutability/collection-immutability-update-example.log.txt +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/extras/immutability/collection-immutability-update-example.log.txt @@ -3,5 +3,5 @@ jakarta.persistence.RollbackException: Error while committing the transaction Caused by: jakarta.persistence.PersistenceException: org.hibernate.HibernateException: Caused by: org.hibernate.HibernateException: changed an immutable collection instance: [ - org.hibernate.userguide.immutability.CollectionImmutabilityTest$Batch.events#1 -] \ No newline at end of file + org.hibernate.orm.test.mapping.mutability.attribute.PluralAttributeMutabilityTest$Batch.events#1 +] diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/immutability.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/immutability.adoc index ba2ec99e16d8..91d3e2b4f954 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/immutability.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/immutability.adoc @@ -1,12 +1,36 @@ -[[entity-immutability]] -=== Immutability +[[mutability]] +=== Mutability :root-project-dir: ../../../../../../.. -:documentation-project-dir: {root-project-dir}/documentation -:example-dir-immutability: {documentation-project-dir}/src/test/java/org/hibernate/userguide/immutability +:core-project-dir: {root-project-dir}/hibernate-core +:mutability-example-dir: {core-project-dir}/src/test/java/org/hibernate/orm/test/mapping/mutability :extrasdir: extras/immutability -Immutability can be specified for both entities and collections. +Immutability can be specified for both entities and attributes. +Unfortunately mutability is an overloaded term. It can refer to either: + +- Whether the internal state of a value can be changed. In this sense, a `java.lang.Date` is considered +mutable because its internal state can be changed by calling `Date#setTime`, whereas `java.lang.String` +is considered immutable because its internal state cannot be changed. Hibernate uses this distinction +for numerous internal optimizations related to dirty checking and making copies. +- Whether the value is updateable in regard to the database. Hibernate can perform other optimizations +based on this distinction. + + +[[mutability-immutable]] +==== @Immutable + +The `@Immutable` annotation declares something immutable in the updateability sense. Mutable (updateable) +is the implicit condition. + +`@Immutable` is allowed on an <>, <>, +<> and <>. Unfortunately, it +has slightly different impacts depending on where it is placed; see the linked sections for details. + + + + +[[mutability-entity]] ==== Entity immutability If a specific entity is immutable, it is good practice to mark it with the `@Immutable` annotation. @@ -15,13 +39,13 @@ If a specific entity is immutable, it is good practice to mark it with the `@Imm ==== [source, JAVA, indent=0] ---- -include::{example-dir-immutability}/EntityImmutabilityTest.java[tags=entity-immutability-example] +include::{mutability-example-dir}/entity/EntityImmutabilityTest.java[tags=entity-immutability-example] ---- ==== Internally, Hibernate is going to perform several optimizations, such as: -- reducing memory footprint since there is no need to retain the dehydrated state for the dirty checking mechanism +- reducing memory footprint since there is no need to retain the loaded state for the dirty checking mechanism - speeding-up the Persistence Context flushing phase since immutable entities can skip the dirty checking process Considering the following entity is persisted in the database: @@ -30,7 +54,7 @@ Considering the following entity is persisted in the database: ==== [source, JAVA, indent=0] ---- -include::{example-dir-immutability}/EntityImmutabilityTest.java[tags=entity-immutability-persist-example] +include::{mutability-example-dir}/entity/EntityImmutabilityTest.java[tags=entity-immutability-persist-example] ---- ==== @@ -41,7 +65,7 @@ Hibernate will skip any modification, therefore no SQL `UPDATE` statement is exe ==== [source, JAVA, indent=0] ---- -include::{example-dir-immutability}/EntityImmutabilityTest.java[tags=entity-immutability-update-example] +include::{mutability-example-dir}/entity/EntityImmutabilityTest.java[tags=entity-immutability-update-example] ---- [source, SQL, indent=0] @@ -50,28 +74,70 @@ include::{extrasdir}/entity-immutability-update-example.sql[] ---- ==== -==== Collection immutability -Just like entities, collections can also be marked with the `@Immutable` annotation. +`@Mutability` is not allowed on an entity. + + -Considering the following entity mappings: -.Immutable collection + +[[mutability-attribute]] +==== Attribute mutability + +The `@Immutable` annotation may also be used on attributes. The impact varies +slightly depending on the exact kind of attribute. + +`@Mutability` on an attribute applies the specified `MutabilityPlan` to the attribute for handling +internal state changes in the values for the attribute. + + +[[mutability-attribute-basic]] +===== Attribute immutability - basic + +When applied to a basic attribute, `@Immutable` implies immutability in both the updateable +and internal-state sense. E.g. + +.Immutable basic attribute ==== [source, JAVA, indent=0] ---- -include::{example-dir-immutability}/CollectionImmutabilityTest.java[tags=collection-immutability-example] +include::{mutability-example-dir}/attribute/BasicAttributeMutabilityTests.java[tags=attribute-immutable-example] ---- ==== -This time, not only the `Event` entity is immutable, but the `Event` collection stored by the `Batch` parent entity. -Once the immutable collection is created, it can never be modified. +Changes to the `theDate` attribute are ignored. + +.Immutable basic attribute change +==== +[source, JAVA, indent=0] +---- +include::{mutability-example-dir}/attribute/BasicAttributeMutabilityTests.java[tags=attribute-immutable-managed-example] +---- +==== + + +[[mutability-attribute-embeddable]] +===== Attribute immutability - embeddable + +To be continued.. + +// todo : document the effect of `@Immutable` on `@Embeddable`, `@Embedded` and `@EmbeddedId` mappings + + +[[mutability-attribute-plural]] +===== Attribute immutability - plural + +Plural attributes (`@ElementCollection`, @OneToMany`, `@ManyToMany` and `@ManyToAny`) may also +be annotated with `@Immutable`. + +TIP:: While most immutable changes are simply discarded, modifying an immutable collection will cause an exception. + .Persisting an immutable collection ==== [source, JAVA, indent=0] ---- -include::{example-dir-immutability}/CollectionImmutabilityTest.java[tags=collection-immutability-persist-example] +include::{mutability-example-dir}/attribute/PluralAttributeMutabilityTest.java[tags=collection-immutability-persist-example] ---- ==== @@ -83,7 +149,7 @@ For instance, we can still modify the entity name: ==== [source, JAVA, indent=0] ---- -include::{example-dir-immutability}/CollectionImmutabilityTest.java[tags=collection-entity-update-example] +include::{mutability-example-dir}/attribute/PluralAttributeMutabilityTest.java[tags=collection-entity-update-example] ---- [source, SQL, indent=0] @@ -98,7 +164,7 @@ However, when trying to modify the `events` collection: ==== [source, JAVA, indent=0] ---- -include::{example-dir-immutability}/CollectionImmutabilityTest.java[tags=collection-immutability-update-example] +include::{mutability-example-dir}/attribute/PluralAttributeMutabilityTest.java[tags=collection-immutability-update-example] ---- [source, bash, indent=0] @@ -107,7 +173,96 @@ include::{extrasdir}/collection-immutability-update-example.log.txt[] ---- ==== -[TIP] + +[[mutability-attribute-entity]] +===== Attribute immutability - entity + +To be continued.. + +// todo : document the effect of `@Immutable` on `@OneToOne`, `@ManyToOne` and `@Any` mappings + + +[[mutability-converter]] +==== AttributeConverter mutability + +Declaring `@Mutability` on an `AttributeConverter` applies the specified `MutabilityPlan` to +all value mappings (attribute, collection element, etc.) to which the converter is applied. + +Declaring `@Immutable` on an `AttributeConverter` is shorthand for declaring `@Mutability` with an +immutable `MutabilityPlan`. + + +[[mutability-usertype]] +==== UserType mutability + +Similar to <> both `@Mutability` and `@Immutable` may +be declared on a `UserType`. + +`@Mutability` applies the specified `MutabilityPlan` to all value mappings (attribute, collection element, etc.) +to which the `UserType` is applied. + + +`@Immutable` applies an immutable `MutabilityPlan` to all value mappings (attribute, collection element, etc.) +to which the `UserType` is applied. + + +[[mutability-mutability]] +==== @Mutability + +`MutabilityPlan` is the contract used by Hibernate to abstract mutability concerns, in the sense of internal state changes. + +A Java type has an inherent `MutabilityPlan` based on its `JavaType#getMutabilityPlan`. + +The `@Mutability` annotation allows a specific `MutabilityPlan` to be used and is allowed on an +attribute, `AttributeConverter` and `UserType`. When used on a `AttributeConverter` or `UserType`, +the specified `MutabilityPlan` is effective for all basic values to which the `AttributeConverter` or +`UserType` is applied. + +To understand the impact of internal-state mutability, consider the following entity: + +.Basic mutability model +==== +[source, JAVA, indent=0] +---- +include::{mutability-example-dir}/MutabilityBaselineEntity.java[tags=mutability-base-entity-example] +---- +==== + +When dealing with an inherently immutable value, such as a `String`, there is only one way to +update the value: + +.Changing immutable value +==== +[source, JAVA, indent=0] +---- +include::{mutability-example-dir}/MutabilityBaselineEntity.java[tags=mutability-base-string-example] +---- +==== + +During flush, this change will make the entity "dirty" and the changes will be written (UPDATE) to +the database. + +When dealing with mutable values, however, Hibernate must be aware of both ways to change the value. First, like +with the immutable value, we can set the new value: + +.Changing mutable value - setting +==== +[source, JAVA, indent=0] +---- +include::{mutability-example-dir}/MutabilityBaselineEntity.java[tags=mutability-base-date-set-example] +---- ==== -While immutable entity changes are simply discarded, modifying an immutable collection will end up in a `HibernateException` being thrown. + +We can also mutate the existing value: + +.Changing mutable value - mutating +==== +[source, JAVA, indent=0] +---- +include::{mutability-example-dir}/MutabilityBaselineEntity.java[tags=mutability-base-date-mutate-example] +---- ==== + +This mutating example has the same effect as the setting example - they each will make the entity dirty. + + diff --git a/documentation/src/test/java/org/hibernate/userguide/immutability/EntityImmutabilityTest.java b/documentation/src/test/java/org/hibernate/userguide/immutability/EntityImmutabilityTest.java deleted file mode 100644 index 345ea9b5fa74..000000000000 --- a/documentation/src/test/java/org/hibernate/userguide/immutability/EntityImmutabilityTest.java +++ /dev/null @@ -1,100 +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 . - */ -package org.hibernate.userguide.immutability; - -import java.util.Date; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; - -import org.hibernate.annotations.Immutable; -import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; - -import org.junit.Test; - -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertEquals; - -/** - * @author Vlad Mihalcea - */ -public class EntityImmutabilityTest extends BaseEntityManagerFunctionalTestCase { - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Event.class - }; - } - - @Test - public void test() { - //tag::entity-immutability-persist-example[] - doInJPA(this::entityManagerFactory, entityManager -> { - Event event = new Event(); - event.setId(1L); - event.setCreatedOn(new Date()); - event.setMessage("Hibernate User Guide rocks!"); - - entityManager.persist(event); - }); - //end::entity-immutability-persist-example[] - //tag::entity-immutability-update-example[] - doInJPA(this::entityManagerFactory, entityManager -> { - Event event = entityManager.find(Event.class, 1L); - log.info("Change event message"); - event.setMessage("Hibernate User Guide"); - }); - doInJPA(this::entityManagerFactory, entityManager -> { - Event event = entityManager.find(Event.class, 1L); - assertEquals("Hibernate User Guide rocks!", event.getMessage()); - }); - //end::entity-immutability-update-example[] - } - - //tag::entity-immutability-example[] - @Entity(name = "Event") - @Immutable - public static class Event { - - @Id - private Long id; - - private Date createdOn; - - private String message; - - //Getters and setters are omitted for brevity - - //end::entity-immutability-example[] - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Date getCreatedOn() { - return createdOn; - } - - public void setCreatedOn(Date createdOn) { - this.createdOn = createdOn; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - //tag::entity-immutability-example[] - } - //end::entity-immutability-example[] -} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/Mutability.java b/hibernate-core/src/main/java/org/hibernate/annotations/Mutability.java index c18ed2812ab5..a763ef4faa51 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/Mutability.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/Mutability.java @@ -20,7 +20,20 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; /** - * Specifies a {@link MutabilityPlan} for a some sort of basic value mapping. + * Specifies a {@link MutabilityPlan} for a basic value mapping. + * + * Mutability refers to whether the internal state of a value can change. + * For example, {@linkplain java.util.Date Date} is considered mutable because its + * internal state can be changed using {@link java.util.Date#setTime} whereas + * {@linkplain java.lang.String String} is considered immutable because its internal + * state cannot be changed. Hibernate uses this distinction when it can for internal + * optimizations. + * + * Hibernate understands the inherent mutability of a large number of Java types - + * {@linkplain java.util.Date Date}, {@linkplain java.lang.String String}, etc. + * {@linkplain Mutability} and friends allow plugging in specific strategies. + * + * * *

Mutability for basic-typed attributes

*

diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java index 1d2328f82029..4afafb3cd687 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BasicValueBinder.java @@ -68,6 +68,7 @@ import org.hibernate.type.BasicType; import org.hibernate.type.SerializableToBlobType; import org.hibernate.type.descriptor.java.BasicJavaType; +import org.hibernate.type.descriptor.java.Immutability; import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.MutabilityPlan; @@ -479,20 +480,21 @@ private void prepareCollectionId(XProperty modelXProperty) { explicitMutabilityAccess = (typeConfiguration) -> { final CollectionIdMutability mutabilityAnn = findAnnotation( modelXProperty, CollectionIdMutability.class ); if ( mutabilityAnn != null ) { - final Class> mutabilityClass = normalizeMutability( mutabilityAnn.value() ); + final Class> mutabilityClass = mutabilityAnn.value(); if ( mutabilityClass != null ) { - if ( useDeferredBeanContainerAccess ) { - return FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( mutabilityClass ); - } - final ManagedBean> jtdBean = beanRegistry.getBean( mutabilityClass ); - return jtdBean.getBeanInstance(); + return resolveMutability( mutabilityClass ); } } - // see if the value's type Class is annotated `@Immutable` + // see if the value's type Class is annotated with mutability-related annotations if ( implicitJavaTypeAccess != null ) { final Class attributeType = ReflectHelper.getClass( implicitJavaTypeAccess.apply( typeConfiguration ) ); if ( attributeType != null ) { + final Mutability attributeTypeMutabilityAnn = attributeType.getAnnotation( Mutability.class ); + if ( attributeTypeMutabilityAnn != null ) { + return resolveMutability( attributeTypeMutabilityAnn.value() ); + } + if ( attributeType.isAnnotationPresent( Immutable.class ) ) { return ImmutableMutabilityPlan.instance(); } @@ -503,11 +505,7 @@ private void prepareCollectionId(XProperty modelXProperty) { if ( converterDescriptor != null ) { final Mutability converterMutabilityAnn = converterDescriptor.getAttributeConverterClass().getAnnotation( Mutability.class ); if ( converterMutabilityAnn != null ) { - if ( useDeferredBeanContainerAccess ) { - return FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( converterMutabilityAnn.value() ); - } - final ManagedBean> jtdBean = beanRegistry.getBean( converterMutabilityAnn.value() ); - return jtdBean.getBeanInstance(); + return resolveMutability( converterMutabilityAnn.value() ); } if ( converterDescriptor.getAttributeConverterClass().isAnnotationPresent( Immutable.class ) ) { @@ -515,17 +513,22 @@ private void prepareCollectionId(XProperty modelXProperty) { } } + // if there is a UserType, see if its Class is annotated with mutability-related annotations final Class> customTypeImpl = Kind.ATTRIBUTE.mappingAccess.customType( modelXProperty ); - if ( customTypeImpl.isAnnotationPresent( Immutable.class ) ) { - return ImmutableMutabilityPlan.instance(); + if ( customTypeImpl != null ) { + final Mutability customTypeMutabilityAnn = customTypeImpl.getAnnotation( Mutability.class ); + if ( customTypeMutabilityAnn != null ) { + return resolveMutability( customTypeMutabilityAnn.value() ); + } + + if ( customTypeImpl.isAnnotationPresent( Immutable.class ) ) { + return ImmutableMutabilityPlan.instance(); + } } // generally, this will trigger usage of the `JavaType#getMutabilityPlan` return null; }; - - // todo (6.0) - handle generator -// final String generator = collectionIdAnn.generator(); } private ManagedBeanRegistry getManagedBeanRegistry() { @@ -600,35 +603,32 @@ private void prepareMapKey( explicitMutabilityAccess = typeConfiguration -> { final MapKeyMutability mutabilityAnn = findAnnotation( mapAttribute, MapKeyMutability.class ); if ( mutabilityAnn != null ) { - final Class> mutabilityClass = normalizeMutability( mutabilityAnn.value() ); + final Class> mutabilityClass = mutabilityAnn.value(); if ( mutabilityClass != null ) { - if ( useDeferredBeanContainerAccess ) { - return FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( mutabilityClass ); - } - final ManagedBean> jtdBean = getManagedBeanRegistry().getBean( mutabilityClass ); - return jtdBean.getBeanInstance(); + return resolveMutability( mutabilityClass ); } } - // see if the value's type Class is annotated `@Immutable` + // see if the value's Java Class is annotated with mutability-related annotations if ( implicitJavaTypeAccess != null ) { final Class attributeType = ReflectHelper.getClass( implicitJavaTypeAccess.apply( typeConfiguration ) ); if ( attributeType != null ) { + final Mutability attributeTypeMutabilityAnn = attributeType.getAnnotation( Mutability.class ); + if ( attributeTypeMutabilityAnn != null ) { + return resolveMutability( attributeTypeMutabilityAnn.value() ); + } + if ( attributeType.isAnnotationPresent( Immutable.class ) ) { return ImmutableMutabilityPlan.instance(); } } } - // if the value is converted, see if the converter Class is annotated `@Immutable` + // if the value is converted, see if converter Class is annotated with mutability-related annotations if ( converterDescriptor != null ) { final Mutability converterMutabilityAnn = converterDescriptor.getAttributeConverterClass().getAnnotation( Mutability.class ); if ( converterMutabilityAnn != null ) { - if ( useDeferredBeanContainerAccess ) { - return FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( converterMutabilityAnn.value() ); - } - final ManagedBean> jtdBean = getManagedBeanRegistry().getBean( converterMutabilityAnn.value() ); - return jtdBean.getBeanInstance(); + return resolveMutability( converterMutabilityAnn.value() ); } if ( converterDescriptor.getAttributeConverterClass().isAnnotationPresent( Immutable.class ) ) { @@ -636,8 +636,14 @@ private void prepareMapKey( } } + // if there is a UserType, see if its Class is annotated with mutability-related annotations final Class> customTypeImpl = Kind.MAP_KEY.mappingAccess.customType( mapAttribute ); if ( customTypeImpl != null ) { + final Mutability customTypeMutabilityAnn = customTypeImpl.getAnnotation( Mutability.class ); + if ( customTypeMutabilityAnn != null ) { + return resolveMutability( customTypeMutabilityAnn.value() ); + } + if ( customTypeImpl.isAnnotationPresent( Immutable.class ) ) { return ImmutableMutabilityPlan.instance(); } @@ -943,53 +949,80 @@ private void normalJdbcTypeDetails(XProperty attributeXProperty) { } private void normalMutabilityDetails(XProperty attributeXProperty) { - explicitMutabilityAccess = typeConfiguration -> { + // Look for `@Mutability` on the attribute final Mutability mutabilityAnn = findAnnotation( attributeXProperty, Mutability.class ); if ( mutabilityAnn != null ) { - final Class> mutability = normalizeMutability( mutabilityAnn.value() ); + final Class> mutability = mutabilityAnn.value(); if ( mutability != null ) { - if ( buildingContext.getBuildingOptions().disallowExtensionsInCdi() ) { - return FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( mutability ); - } - return getManagedBeanRegistry().getBean( mutability ).getBeanInstance(); + return resolveMutability( mutability ); } } + // Look for `@Immutable` on the attribute final Immutable immutableAnn = attributeXProperty.getAnnotation( Immutable.class ); if ( immutableAnn != null ) { return ImmutableMutabilityPlan.instance(); } - // see if the value's type Class is annotated `@Immutable` - if ( implicitJavaTypeAccess != null ) { - final Class attributeType = ReflectHelper.getClass( implicitJavaTypeAccess.apply( typeConfiguration ) ); + // Look for `@Mutability` on the attribute's type + if ( explicitJavaTypeAccess != null || implicitJavaTypeAccess != null ) { + Class attributeType = null; + if ( explicitJavaTypeAccess != null ) { + final BasicJavaType jtd = explicitJavaTypeAccess.apply( typeConfiguration ); + if ( jtd != null ) { + attributeType = jtd.getJavaTypeClass(); + } + } + if ( attributeType == null ) { + final java.lang.reflect.Type javaType = implicitJavaTypeAccess.apply( typeConfiguration ); + if ( javaType != null ) { + attributeType = ReflectHelper.getClass( javaType ); + } + } + if ( attributeType != null ) { - if ( attributeType.isAnnotationPresent( Immutable.class ) ) { + final Mutability classMutability = attributeType.getAnnotation( Mutability.class ); + + if ( classMutability != null ) { + final Class> mutability = classMutability.value(); + if ( mutability != null ) { + return resolveMutability( mutability ); + } + } + + final Immutable classImmutable = attributeType.getAnnotation( Immutable.class ); + if ( classImmutable != null ) { return ImmutableMutabilityPlan.instance(); } } } - // if the value is converted, see if the converter Class is annotated `@Immutable` + // if the value is converted, see if the converter Class is annotated `@Mutability` if ( converterDescriptor != null ) { final Mutability converterMutabilityAnn = converterDescriptor.getAttributeConverterClass().getAnnotation( Mutability.class ); if ( converterMutabilityAnn != null ) { - if ( buildingContext.getBuildingOptions().disallowExtensionsInCdi() ) { - return FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( converterMutabilityAnn.value() ); - } - final ManagedBean> jtdBean = getManagedBeanRegistry().getBean( converterMutabilityAnn.value() ); - return jtdBean.getBeanInstance(); + final Class> mutability = converterMutabilityAnn.value(); + return resolveMutability( mutability ); } - if ( converterDescriptor.getAttributeConverterClass().isAnnotationPresent( Immutable.class ) ) { + final Immutable converterImmutableAnn = converterDescriptor.getAttributeConverterClass().getAnnotation( Immutable.class ); + if ( converterImmutableAnn != null ) { return ImmutableMutabilityPlan.instance(); } } + // if a custom UserType is specified, see if the UserType Class is annotated `@Mutability` final Class> customTypeImpl = Kind.ATTRIBUTE.mappingAccess.customType( attributeXProperty ); if ( customTypeImpl != null ) { - if ( customTypeImpl.isAnnotationPresent( Immutable.class ) ) { + final Mutability customTypeMutabilityAnn = customTypeImpl.getAnnotation( Mutability.class ); + if ( customTypeMutabilityAnn != null ) { + final Class> mutability = customTypeMutabilityAnn.value(); + return resolveMutability( mutability ); + } + + final Immutable customTypeImmutableAnn = customTypeImpl.getAnnotation( Immutable.class ); + if ( customTypeImmutableAnn != null ) { return ImmutableMutabilityPlan.instance(); } } @@ -999,6 +1032,23 @@ private void normalMutabilityDetails(XProperty attributeXProperty) { }; } + @SuppressWarnings({ "rawtypes", "unchecked" }) + private MutabilityPlan resolveMutability(Class mutability) { + if ( mutability.equals( Immutability.class ) ) { + return Immutability.instance(); + } + + if ( mutability.equals( ImmutableMutabilityPlan.class ) ) { + return ImmutableMutabilityPlan.instance(); + } + + if ( buildingContext.getBuildingOptions().disallowExtensionsInCdi() ) { + return FallbackBeanInstanceProducer.INSTANCE.produceBeanInstance( mutability ); + } + + return getManagedBeanRegistry().getBean( mutability ).getBeanInstance(); + } + private void normalSupplementalDetails(XProperty attributeXProperty) { explicitJavaTypeAccess = typeConfiguration -> { @@ -1067,10 +1117,6 @@ private static Class> normalizeJavaType(Class> normalizeMutability(Class> mutability) { - return mutability; - } - private java.lang.reflect.Type resolveJavaType(XClass returnedClassOrElement) { return buildingContext.getBootstrapContext() .getReflectionManager() diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java index 6c24c4c388fe..486dcfed2283 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/EntityBinder.java @@ -17,27 +17,6 @@ import java.util.Map; import java.util.Set; -import jakarta.persistence.Access; -import jakarta.persistence.AttributeOverride; -import jakarta.persistence.AttributeOverrides; -import jakarta.persistence.Cacheable; -import jakarta.persistence.ConstraintMode; -import jakarta.persistence.DiscriminatorColumn; -import jakarta.persistence.DiscriminatorValue; -import jakarta.persistence.Entity; -import jakarta.persistence.IdClass; -import jakarta.persistence.Inheritance; -import jakarta.persistence.InheritanceType; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.NamedEntityGraph; -import jakarta.persistence.NamedEntityGraphs; -import jakarta.persistence.PrimaryKeyJoinColumn; -import jakarta.persistence.PrimaryKeyJoinColumns; -import jakarta.persistence.SecondaryTable; -import jakarta.persistence.SecondaryTables; -import jakarta.persistence.SharedCacheMode; -import jakarta.persistence.UniqueConstraint; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.MappingException; @@ -56,6 +35,7 @@ import org.hibernate.annotations.HQLSelect; import org.hibernate.annotations.Immutable; import org.hibernate.annotations.Loader; +import org.hibernate.annotations.Mutability; import org.hibernate.annotations.NaturalIdCache; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OptimisticLockType; @@ -125,6 +105,28 @@ import org.jboss.logging.Logger; +import jakarta.persistence.Access; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.AttributeOverrides; +import jakarta.persistence.Cacheable; +import jakarta.persistence.ConstraintMode; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.IdClass; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.NamedEntityGraphs; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.PrimaryKeyJoinColumns; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.SecondaryTables; +import jakarta.persistence.SharedCacheMode; +import jakarta.persistence.UniqueConstraint; + import static org.hibernate.boot.model.internal.AnnotatedClassType.MAPPED_SUPERCLASS; import static org.hibernate.boot.model.internal.AnnotatedDiscriminatorColumn.buildDiscriminatorColumn; import static org.hibernate.boot.model.internal.AnnotatedJoinColumn.buildInheritanceJoinColumn; @@ -132,10 +134,10 @@ import static org.hibernate.boot.model.internal.BinderHelper.getOverridableAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.hasToOneAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.isDefault; -import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator; import static org.hibernate.boot.model.internal.BinderHelper.toAliasEntityMap; import static org.hibernate.boot.model.internal.BinderHelper.toAliasTableMap; import static org.hibernate.boot.model.internal.EmbeddableBinder.fillEmbeddable; +import static org.hibernate.boot.model.internal.GeneratorBinder.makeIdGenerator; import static org.hibernate.boot.model.internal.HCANNHelper.findContainingAnnotations; import static org.hibernate.boot.model.internal.InheritanceState.getInheritanceStateOfSuperEntity; import static org.hibernate.boot.model.internal.PropertyBinder.addElementsOfClass; @@ -1190,6 +1192,8 @@ else if ( isMutable() ) { LOG.immutableAnnotationOnNonRoot( annotatedClass.getName() ); } + ensureNoMutabilityPlan(); + bindCustomPersister(); bindCustomSql(); bindSynchronize(); @@ -1200,6 +1204,12 @@ else if ( isMutable() ) { processNamedEntityGraphs(); } + private void ensureNoMutabilityPlan() { + if ( annotatedClass.isAnnotationPresent( Mutability.class ) ) { + throw new MappingException( "@Mutability is not allowed on entity" ); + } + } + private boolean isMutable() { return !annotatedClass.isAnnotationPresent(Immutable.class); } 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 6a0b2a3081f9..ed65e87351e0 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 @@ -8,29 +8,28 @@ import java.io.Serializable; import java.lang.reflect.Type; +import java.util.function.Function; import java.util.function.Supplier; -import jakarta.persistence.EnumType; -import jakarta.persistence.TemporalType; - import org.hibernate.MappingException; import org.hibernate.dialect.Dialect; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Column; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Table; -import org.hibernate.type.descriptor.converter.internal.NamedEnumValueConverter; -import org.hibernate.type.descriptor.converter.internal.OrdinalEnumValueConverter; import org.hibernate.tool.schema.extract.spi.ColumnTypeInformation; import org.hibernate.type.AdjustableBasicType; import org.hibernate.type.BasicType; import org.hibernate.type.CustomType; import org.hibernate.type.SerializableType; -import org.hibernate.type.descriptor.java.BasicPluralJavaType; +import org.hibernate.type.descriptor.converter.internal.NamedEnumValueConverter; +import org.hibernate.type.descriptor.converter.internal.OrdinalEnumValueConverter; import org.hibernate.type.descriptor.java.BasicJavaType; +import org.hibernate.type.descriptor.java.BasicPluralJavaType; import org.hibernate.type.descriptor.java.EnumJavaType; import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan; import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.descriptor.java.MutabilityPlan; import org.hibernate.type.descriptor.java.SerializableJavaType; import org.hibernate.type.descriptor.java.TemporalJavaType; import org.hibernate.type.descriptor.jdbc.JdbcType; @@ -38,6 +37,9 @@ import org.hibernate.type.descriptor.jdbc.ObjectJdbcType; import org.hibernate.type.spi.TypeConfiguration; +import jakarta.persistence.EnumType; +import jakarta.persistence.TemporalType; + import static org.hibernate.type.SqlTypes.SMALLINT; import static org.hibernate.type.SqlTypes.TINYINT; @@ -52,6 +54,7 @@ public static BasicValue.Resolution from( JdbcType explicitJdbcType, Type resolvedJavaType, Supplier> reflectedJtdResolver, + Function explicitMutabilityPlanAccess, JdbcTypeIndicators stdIndicators, Table table, Selectable selectable, @@ -84,6 +87,7 @@ else if ( explicitJavaType instanceof TemporalJavaType ) { null, explicitJdbcType, resolvedJavaType, + explicitMutabilityPlanAccess, stdIndicators, typeConfiguration ); @@ -135,6 +139,7 @@ else if ( reflectedJtd instanceof TemporalJavaType ) { null, explicitJdbcType, resolvedJavaType, + explicitMutabilityPlanAccess, stdIndicators, typeConfiguration ); @@ -171,6 +176,7 @@ else if ( elementJtd instanceof TemporalJavaType ) { null, null, resolvedJavaType, + explicitMutabilityPlanAccess, stdIndicators, typeConfiguration ); @@ -296,7 +302,7 @@ else if ( column.getLength() != null ) { jdbcMapping.getJavaTypeDescriptor(), jdbcMapping.getJdbcType(), jdbcMapping, - null + determineMutabilityPlan( explicitMutabilityPlanAccess, jdbcMapping.getJavaTypeDescriptor(), typeConfiguration ) ); } @@ -477,6 +483,7 @@ public static InferredBasicValueResolution fromTemporal( BasicJavaType explicitJavaType, JdbcType explicitJdbcType, Type resolvedJavaType, + Function explicitMutabilityPlanAccess, JdbcTypeIndicators stdIndicators, TypeConfiguration typeConfiguration) { final TemporalType requestedTemporalPrecision = stdIndicators.getTemporalPrecision(); @@ -509,13 +516,14 @@ public static InferredBasicValueResolution fromTemporal( final BasicType jdbcMapping = typeConfiguration.getBasicTypeRegistry().resolve( explicitTemporalJtd, jdbcType ); + final MutabilityPlan mutabilityPlan = determineMutabilityPlan( explicitMutabilityPlanAccess, explicitTemporalJtd, typeConfiguration ); return new InferredBasicValueResolution<>( jdbcMapping, explicitTemporalJtd, explicitTemporalJtd, jdbcType, jdbcMapping, - explicitTemporalJtd.getMutabilityPlan() + mutabilityPlan ); } @@ -575,8 +583,22 @@ public static InferredBasicValueResolution fromTemporal( basicType.getJavaTypeDescriptor(), basicType.getJdbcType(), basicType, - reflectedJtd.getMutabilityPlan() + determineMutabilityPlan( explicitMutabilityPlanAccess, reflectedJtd, typeConfiguration ) ); } + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static MutabilityPlan determineMutabilityPlan( + Function explicitMutabilityPlanAccess, + JavaType jtd, + TypeConfiguration typeConfiguration) { + if ( explicitMutabilityPlanAccess != null ) { + final MutabilityPlan mutabilityPlan = explicitMutabilityPlanAccess.apply( typeConfiguration ); + if ( mutabilityPlan != null ) { + return mutabilityPlan; + } + } + return jtd.getMutabilityPlan(); + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java index ca29347a6b2f..66f15972bcb6 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/BasicValue.java @@ -479,6 +479,7 @@ public TypeConfiguration getTypeConfiguration() { jdbcType, resolvedJavaType, this::determineReflectedJavaType, + explicitMutabilityPlanAccess, this, getTable(), column, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java index d0dc0f8acfcb..6dd44bb46a20 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java @@ -199,7 +199,7 @@ public static BasicAttributeMapping buildBasicAttributeMapping( MappingModelCreationProcess creationProcess) { final SimpleValue value = (SimpleValue) bootProperty.getValue(); final BasicValue.Resolution resolution = ( (Resolvable) value ).resolve(); - SimpleAttributeMetadata attributeMetadata = new SimpleAttributeMetadata( propertyAccess, resolution.getMutabilityPlan(), bootProperty, value ); + final SimpleAttributeMetadata attributeMetadata = new SimpleAttributeMetadata( propertyAccess, resolution.getMutabilityPlan(), bootProperty, value ); final FetchTiming fetchTiming; final FetchStyle fetchStyle; diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/Immutability.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/Immutability.java new file mode 100644 index 000000000000..45f3a1f4d4bb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/Immutability.java @@ -0,0 +1,52 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.type.descriptor.java; + +import java.io.Serializable; + +import org.hibernate.SharedSessionContract; +import org.hibernate.annotations.Mutability; + +/** + * Object-typed form of {@link ImmutableMutabilityPlan} for easier use + * with {@link Mutability} for users + * + * @see org.hibernate.annotations.Immutable + * + * @author Steve Ebersole + */ +public class Immutability implements MutabilityPlan { + /** + * Singleton access + */ + public static final Immutability INSTANCE = new Immutability(); + + public static MutabilityPlan instance() { + //noinspection unchecked + return (MutabilityPlan) INSTANCE; + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public Object deepCopy(Object value) { + return value; + } + + @Override + public Serializable disassemble(Object value, SharedSessionContract session) { + return (Serializable) value; + } + + @Override + public Object assemble(Serializable cached, SharedSessionContract session) { + return cached; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ImmutableMutabilityPlan.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ImmutableMutabilityPlan.java index 365117f1edde..b475340471b5 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ImmutableMutabilityPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ImmutableMutabilityPlan.java @@ -13,6 +13,11 @@ /** * Mutability plan for immutable objects * + * @apiNote For use with {@link org.hibernate.annotations.Mutability}, + * users should instead use {@link Immutability} as the type parameterization + * here does not work with the parameterization defined on + * {@link org.hibernate.annotations.Mutability#value} + * * @author Steve Ebersole */ public class ImmutableMutabilityPlan implements MutabilityPlan { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapImmutableTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapImmutableTests.java deleted file mode 100644 index 152487fe3a7d..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapImmutableTests.java +++ /dev/null @@ -1,165 +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.orm.test.mapping.converted.converter.mutabiity; - -import java.util.Map; - -import org.hibernate.annotations.Immutable; -import org.hibernate.internal.util.collections.CollectionHelper; - -import org.hibernate.testing.jdbc.SQLStatementInspector; -import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.FailureExpected; -import org.hibernate.testing.orm.junit.JiraKey; -import org.hibernate.testing.orm.junit.SessionFactory; -import org.hibernate.testing.orm.junit.SessionFactoryScope; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import jakarta.persistence.Column; -import jakarta.persistence.Convert; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Steve Ebersole - */ -@DomainModel( annotatedClasses = ConvertedMapImmutableTests.TestEntity.class ) -@SessionFactory( useCollectingStatementInspector = true ) -public class ConvertedMapImmutableTests { - - @Test - @JiraKey( "HHH-16081" ) - void testManagedUpdate(SessionFactoryScope scope) { - final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); - - scope.inTransaction( (session) -> { - final TestEntity loaded = session.get( TestEntity.class, 1 ); - loaded.values.put( "ghi", "789" ); - statementInspector.clear(); - } ); - - final TestEntity after = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); - assertThat( after.values ).hasSize( 2 ); - } - - @Test - @JiraKey( "HHH-16081" ) - @FailureExpected( reason = "Fails due to HHH-16132 - Hibernate believes the attribute is dirty, even though it is immutable." ) - void testMerge(SessionFactoryScope scope) { - final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); - - final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); - assertThat( loaded.values ).hasSize( 2 ); - - loaded.values.put( "ghi", "789" ); - statementInspector.clear(); - scope.inTransaction( (session) -> session.merge( loaded ) ); - - final TestEntity merged = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); - assertThat( merged.values ).hasSize( 2 ); - } - - @Test - @JiraKey( "HHH-16132" ) - @FailureExpected( reason = "Fails due to HHH-16132 - Hibernate believes the attribute is dirty, even though it is immutable." ) - void testDirtyChecking(SessionFactoryScope scope) { - final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); - - // make changes to a managed entity - should not trigger update since it is immutable - scope.inTransaction( (session) -> { - final TestEntity managed = session.get( TestEntity.class, 1 ); - statementInspector.clear(); - assertThat( managed.values ).hasSize( 2 ); - // make the change - managed.values.put( "ghi", "789" ); - } ); - assertThat( statementInspector.getSqlQueries() ).isEmpty(); - - // make no changes to a detached entity and merge it - should not trigger update - final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); - assertThat( loaded.values ).hasSize( 2 ); - // make the change - loaded.values.put( "ghi", "789" ); - statementInspector.clear(); - scope.inTransaction( (session) -> session.merge( loaded ) ); - // the SELECT - assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); - } - - @Test - @JiraKey( "HHH-16132" ) - void testNotDirtyChecking(SessionFactoryScope scope) { - final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); - - // make changes to a managed entity - should not trigger update - scope.inTransaction( (session) -> { - final TestEntity managed = session.get( TestEntity.class, 1 ); - statementInspector.clear(); - assertThat( managed.values ).hasSize( 2 ); - } ); - assertThat( statementInspector.getSqlQueries() ).isEmpty(); - - // make no changes to a detached entity and merge it - should not trigger update - final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); - assertThat( loaded.values ).hasSize( 2 ); - statementInspector.clear(); - scope.inTransaction( (session) -> session.merge( loaded ) ); - // the SELECT - assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); - } - - @BeforeEach - void createTestData(SessionFactoryScope scope) { - scope.inTransaction( (session) -> { - session.persist( new TestEntity( - 1, - CollectionHelper.toMap( - "abc", "123", - "def", "456" - ) - ) ); - } ); - } - - @AfterEach - void dropTestData(SessionFactoryScope scope) { - scope.inTransaction( (session) -> { - session.createMutationQuery( "delete TestEntity" ).executeUpdate(); - } ); - } - - @Immutable - public static class ImmutableMapConverter extends ConvertedMapMutableTests.MapConverter { - } - - @Entity( name = "TestEntity" ) - @Table( name = "entity_immutable_map" ) - public static class TestEntity { - @Id - private Integer id; - - @Convert( converter = ImmutableMapConverter.class ) - @Column( name="vals" ) - private Map values; - - private TestEntity() { - // for use by Hibernate - } - - public TestEntity( - Integer id, - Map values) { - this.id = id; - this.values = values; - } - } -} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMutabilityTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMutabilityTests.java deleted file mode 100644 index 70b70706c3e8..000000000000 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMutabilityTests.java +++ /dev/null @@ -1,188 +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.orm.test.mapping.converted.converter.mutabiity; - -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.Date; - -import org.hibernate.annotations.Immutable; -import org.hibernate.internal.util.StringHelper; - -import org.hibernate.testing.jdbc.SQLStatementInspector; -import org.hibernate.testing.orm.junit.DomainModel; -import org.hibernate.testing.orm.junit.JiraKey; -import org.hibernate.testing.orm.junit.SessionFactory; -import org.hibernate.testing.orm.junit.SessionFactoryScope; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Convert; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Steve Ebersole - */ -@JiraKey( "HHH-16081" ) -@DomainModel( annotatedClasses = ConvertedMutabilityTests.TestEntityWithDates.class ) -@SessionFactory( useCollectingStatementInspector = true ) -public class ConvertedMutabilityTests { - private static final Instant START = Instant.now(); - - @Test - void testImmutableDate(SessionFactoryScope scope) { - final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); - - scope.inTransaction( (session) -> { - final TestEntityWithDates loaded = session.get( TestEntityWithDates.class, 1 ); - - statementInspector.clear(); - - // change `d2` - because it is immutable, this should not trigger an update - loaded.d2.setTime( Instant.EPOCH.toEpochMilli() ); - } ); - - assertThat( statementInspector.getSqlQueries() ).isEmpty(); - - scope.inTransaction( (session) -> { - final TestEntityWithDates loaded = session.get( TestEntityWithDates.class, 1 ); - assertThat( loaded.d1.getTime() ).isEqualTo( START.toEpochMilli() ); - } ); - } - - @Test - void testMutableDate(SessionFactoryScope scope) { - final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); - - scope.inTransaction( (session) -> { - final TestEntityWithDates loaded = session.get( TestEntityWithDates.class, 1 ); - - statementInspector.clear(); - - // change `d1` - because it is mutable, this should trigger an update - loaded.d1.setTime( Instant.EPOCH.toEpochMilli() ); - } ); - - assertThat( statementInspector.getSqlQueries() ).isNotEmpty(); - - scope.inTransaction( (session) -> { - final TestEntityWithDates loaded = session.get( TestEntityWithDates.class, 1 ); - assertThat( loaded.d1.getTime() ).isEqualTo( Instant.EPOCH.toEpochMilli() ); - } ); - } - - @Test - void testDatesWithMerge(SessionFactoryScope scope) { - final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); - final TestEntityWithDates loaded = scope.fromTransaction( (session) -> session.get( TestEntityWithDates.class, 1 ) ); - - loaded.d1.setTime( Instant.EPOCH.toEpochMilli() ); - - statementInspector.clear(); - scope.inTransaction( (session) -> session.merge( loaded ) ); - assertThat( statementInspector.getSqlQueries() ).isNotEmpty(); - - final TestEntityWithDates loaded2 = scope.fromTransaction( (session) -> session.get( TestEntityWithDates.class, 1 ) ); - assertThat( loaded2.d1.getTime() ).isEqualTo( Instant.EPOCH.toEpochMilli() ); - - loaded2.d2.setTime( Instant.EPOCH.toEpochMilli() ); - statementInspector.clear(); - scope.inTransaction( (session) -> session.merge( loaded ) ); - assertThat( statementInspector.getSqlQueries() ).isNotEmpty(); - - final TestEntityWithDates loaded3 = scope.fromTransaction( (session) -> session.get( TestEntityWithDates.class, 1 ) ); - assertThat( loaded3.d2.getTime() ).isEqualTo( START.toEpochMilli() ); - } - - @BeforeEach - void createTestData(SessionFactoryScope scope) { - scope.inTransaction( (session) -> { - session.persist( new TestEntityWithDates( - 1, - Date.from( START ), - Date.from( START ) - ) ); - } ); - } - - @AfterEach - void dropTestData(SessionFactoryScope scope) { - scope.inTransaction( (session) -> { - session.createMutationQuery( "delete TestEntityWithDates" ).executeUpdate(); - } ); - } - - public static class DateConverter implements AttributeConverter { - @Override - public String convertToDatabaseColumn(Date date) { - if ( date == null ) { - return null; - } - return DateTimeFormatter.ISO_INSTANT.format( date.toInstant() ); - } - - @Override - public Date convertToEntityAttribute(String date) { - if ( StringHelper.isEmpty( date ) ) { - return null; - } - return Date.from( Instant.from( DateTimeFormatter.ISO_INSTANT.parse( date ) ) ); - } - } - - @Immutable - public static class ImmutableDateConverter implements AttributeConverter { - @Override - public String convertToDatabaseColumn(Date date) { - if ( date == null ) { - return null; - } - return DateTimeFormatter.ISO_INSTANT.format( date.toInstant() ); - } - - @Override - public Date convertToEntityAttribute(String date) { - if ( StringHelper.isEmpty( date ) ) { - return null; - } - return Date.from( Instant.from( DateTimeFormatter.ISO_INSTANT.parse( date ) ) ); - } - } - - - @Entity( name = "TestEntityWithDates" ) - @Table( name = "entity_dates" ) - public static class TestEntityWithDates { - @Id - private Integer id; - - @Convert( converter = DateConverter.class ) - private Date d1; - @Convert( converter = ImmutableDateConverter.class ) - private Date d2; - - private TestEntityWithDates() { - // for use by Hibernate - } - - public TestEntityWithDates( - Integer id, - Date d1, - Date d2) { - this.id = id; - this.d1 = d1; - this.d2 = d2; - } - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/MutabilityBaselineEntity.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/MutabilityBaselineEntity.java new file mode 100644 index 000000000000..b12e947413f3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/MutabilityBaselineEntity.java @@ -0,0 +1,98 @@ +/* + * 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.orm.test.mapping.mutability; + +import java.time.Instant; +import java.util.Date; + +import org.hibernate.Session; + +import jakarta.persistence.Basic; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +/** + * Used as example entity in UG + * + * @author Steve Ebersole + */ +//tag::mutability-base-entity-example[] +@Entity +public class MutabilityBaselineEntity { + @Id + private Integer id; + @Basic + private String name; + @Basic + private Date activeTimestamp; +//end::mutability-base-entity-example[] + + private MutabilityBaselineEntity() { + // for Hibernate use + } + + public MutabilityBaselineEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Date getActiveTimestamp() { + return activeTimestamp; + } + + public void setActiveTimestamp(Date activeTimestamp) { + this.activeTimestamp = activeTimestamp; + } + + void stringExample() { + //tag::mutability-base-string-example[] + Session session = getSession(); + MutabilityBaselineEntity entity = session.find( MutabilityBaselineEntity.class, 1 ); + entity.setName( "new name" ); + //end::mutability-base-string-example[] + } + + private void dateExampleSet() { + //tag::mutability-base-date-set-example[] + Session session = getSession(); + MutabilityBaselineEntity entity = session.find( MutabilityBaselineEntity.class, 1 ); + entity.setActiveTimestamp( now() ); + //end::mutability-base-date-set-example[] + } + + private void dateExampleMutate() { + //tag::mutability-base-date-mutate-example[] + Session session = getSession(); + MutabilityBaselineEntity entity = session.find( MutabilityBaselineEntity.class, 1 ); + entity.getActiveTimestamp().setTime( now().getTime() ); + //end::mutability-base-date-mutate-example[] + } + + private Session getSession() { + return null; + } + + private Date now() { + return Date.from( Instant.now() ); + } + +//tag::mutability-base-entity-example[] +} +//end::mutability-base-entity-example[] + diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/BasicAttributeMutabilityTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/BasicAttributeMutabilityTests.java new file mode 100644 index 000000000000..c803d58d6818 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/BasicAttributeMutabilityTests.java @@ -0,0 +1,260 @@ +/* + * 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.orm.test.mapping.mutability.attribute; + +import java.time.Instant; +import java.util.Date; + +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Mutability; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.descriptor.java.Immutability; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tested premises: + * + * 1. `@Immutable` on a basic attribute - + * * not-updateable + * * immutable + * 2. `@Mutability(Immutability.class)` on basic attribute + * * updateable + * * immutable + * + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = BasicAttributeMutabilityTests.TheEntity.class ) +@SessionFactory +public class BasicAttributeMutabilityTests { + private static final Instant START = Instant.now(); + + @Test + public void verifyDomainModel(DomainModelScope domainModelScope, SessionFactoryScope sfSessionFactoryScope) { + final PersistentClass persistentClass = domainModelScope + .getDomainModel() + .getEntityBinding( TheEntity.class.getName() ); + final EntityPersister entityDescriptor = sfSessionFactoryScope.getSessionFactory() + .getRuntimeMetamodels() + .getMappingMetamodel() + .getEntityDescriptor( TheEntity.class ); + + // `@Immutable` + final Property theDateProperty = persistentClass.getProperty( "theDate" ); + assertThat( theDateProperty.isUpdateable() ).isFalse(); + final AttributeMapping theDateAttribute = entityDescriptor.findAttributeMapping( "theDate" ); + assertThat( theDateAttribute.getExposedMutabilityPlan().isMutable() ).isFalse(); + + // `@Mutability(Immutability.class)` + final Property anotherDateProperty = persistentClass.getProperty( "anotherDate" ); + assertThat( anotherDateProperty.isUpdateable() ).isTrue(); + final AttributeMapping anotherDateAttribute = entityDescriptor.findAttributeMapping( "anotherDate" ); + assertThat( anotherDateAttribute.getExposedMutabilityPlan().isMutable() ).isFalse(); + } + + /** + * `@Immutable` attribute while managed - no update + */ + @Test + public void testImmutableManaged(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "@Immutable test", Date.from( START ) ) ); + } ); + + // try to update the managed form + scope.inTransaction( (session) -> { + //tag::attribute-immutable-managed-example[] + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + // this change will be ignored + theEntity.theDate.setTime( Instant.EPOCH.toEpochMilli() ); + //end::attribute-immutable-managed-example[] + } ); + + // verify the value did not change + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.theDate.getTime() ).isEqualTo( START.toEpochMilli() ); + } ); + } + + /** + * `@Immutable` attribute on merged detached value - no update (its non-updateable) + */ + @Test + public void testImmutableDetached(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "@Immutable test", Date.from( START ) ) ); + } ); + + // load a detached reference + final TheEntity detached = scope.fromTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.theDate.getTime() ).isEqualTo( START.toEpochMilli() ); + return theEntity; + } ); + + // make the change again, this time to a detached instance and merge it. + //tag::attribute-immutable-merge-example[] + detached.theDate.setTime( Instant.EPOCH.toEpochMilli() ); + scope.inTransaction( (session) -> session.merge( detached ) ); + //end::attribute-immutable-merge-example[] + + // verify the value did not change via the merge + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.theDate.getTime() ).isEqualTo( START.toEpochMilli() ); + } ); + } + + /** + * `@Mutability(Immutability.class)` attribute while managed - no update + */ + @Test + public void testImmutabilityManaged(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "@Mutability test", Date.from( START ) ) ); + } ); + + // try to update the managed form + scope.inTransaction( (session) -> { + //tag::attribute-immutability-managed-example[] + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + theEntity.anotherDate.setTime( Instant.EPOCH.toEpochMilli() ); + //end::attribute-immutability-managed-example[] + } ); + + // reload it and verify the value did not change + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.anotherDate.getTime() ).isEqualTo( START.toEpochMilli() ); + } ); + } + + /** + * `@Mutability(Immutability.class)` attribute while managed - update because + * we cannot distinguish one type of change from another while detached + */ + @Test + public void testImmutabilityDetached(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "@Mutability test", Date.from( START ) ) ); + } ); + + // load a detached reference + final TheEntity detached = scope.fromTransaction( (session) -> session.find( TheEntity.class, 1 ) ); + + //tag::attribute-immutability-merge-example[] + detached.anotherDate.setTime( Instant.EPOCH.toEpochMilli() ); + scope.inTransaction( (session) -> session.merge( detached ) ); + //end::attribute-immutability-merge-example[] + + // verify the change was persisted + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.anotherDate.getTime() ).isEqualTo( Instant.EPOCH.toEpochMilli() ); + } ); + } + + /** + * Normal mutable value while managed + */ + @Test + public void testMutableManaged(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "Baseline mutable test", Date.from( START ) ) ); + } ); + + // try to mutate the managed form + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + theEntity.mutableDate.setTime( Instant.EPOCH.toEpochMilli() ); + } ); + + // verify the value did change + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.mutableDate.getTime() ).isEqualTo( Instant.EPOCH.toEpochMilli() ); + } ); + } + + /** + * Normal mutable value while detached + */ + @Test + public void testMutableMerge(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "Baseline mutable test", Date.from( START ) ) ); + } ); + + // load a detached reference + final TheEntity detached = scope.fromTransaction( (session) -> session.find( TheEntity.class, 1 ) ); + + // now mutate the detached state and merge + detached.mutableDate.setTime( START.toEpochMilli() ); + scope.inTransaction( (session) -> session.merge( detached ) ); + + // verify the value did change + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.mutableDate.getTime() ).isEqualTo( START.toEpochMilli() ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> session.createMutationQuery( "delete TheEntity" ).executeUpdate() ); + } + + @Entity( name = "TheEntity" ) + @Table( name = "TheEntity" ) + public static class TheEntity { + @Id + private Integer id; + + @Basic + private String name; + + //tag::attribute-immutable-example[] + @Immutable + private Date theDate; + //end::attribute-immutable-example[] + + //tag::attribute-immutability-example[] + @Mutability(Immutability.class) + private Date anotherDate; + //end::attribute-immutability-example[] + + private Date mutableDate; + + private TheEntity() { + // for use by Hibernate + } + + public TheEntity(Integer id, String name, Date aDate) { + this.id = id; + this.name = name; + this.theDate = aDate; + this.anotherDate = aDate; + this.mutableDate = aDate; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/EntityAttributeMutabilityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/EntityAttributeMutabilityTest.java new file mode 100644 index 000000000000..634c17d306f3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/EntityAttributeMutabilityTest.java @@ -0,0 +1,184 @@ +/* + * 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.orm.test.mapping.mutability.attribute; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Mutability; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.descriptor.java.Immutability; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = EntityAttributeMutabilityTest.Employee.class ) +@SessionFactory +public class EntityAttributeMutabilityTest { + + @Test + public void verifyMetamodel(DomainModelScope domainModelScope, SessionFactoryScope sessionFactoryScope) { + final PersistentClass persistentClass = domainModelScope.getEntityBinding( Employee.class ); + final EntityPersister entityDescriptor = sessionFactoryScope.getSessionFactory() + .getRuntimeMetamodels() + .getMappingMetamodel() + .getEntityDescriptor( Employee.class ); + + // `@Immutable` + final Property managerProperty = persistentClass.getProperty( "manager" ); + assertThat( managerProperty.isUpdateable() ).isFalse(); + final AttributeMapping managerAttribute = entityDescriptor.findAttributeMapping( "manager" ); + assertThat( managerAttribute.getExposedMutabilityPlan().isMutable() ).isFalse(); + + // `@Mutability(Immutability.class)` - no effect + final Property manager2Property = persistentClass.getProperty( "manager2" ); + assertThat( manager2Property.isUpdateable() ).isTrue(); + final AttributeMapping manager2Attribute = entityDescriptor.findAttributeMapping( "manager2" ); + assertThat( manager2Attribute.getExposedMutabilityPlan().isMutable() ).isTrue(); + } + + @Entity( name = "Employee" ) + @Table( name = "Employee" ) + public static class Employee { + @Id + private Integer id; + @Basic + private String name; + @ManyToOne + @JoinColumn( name = "manager_fk" ) + @Immutable + private Employee manager; + @ManyToOne + @JoinColumn( name = "manager2_fk" ) + @Mutability(Immutability.class) + private Employee manager2; + + private Employee() { + // for use by Hibernate + } + + public Employee(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + //tag::collection-immutability-example[] + @Entity(name = "Batch") + public static class Batch { + + @Id + private Long id; + + private String name; + + @OneToMany(cascade = CascadeType.ALL) + @Immutable + private List events = new ArrayList<>(); + + //Getters and setters are omitted for brevity + + //end::collection-immutability-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getEvents() { + return events; + } + //tag::collection-immutability-example[] + } + + @Entity(name = "Event") + @Immutable + public static class Event { + + @Id + private Long id; + + private Date createdOn; + + private String message; + + //Getters and setters are omitted for brevity + + //end::collection-immutability-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + //tag::collection-immutability-example[] + } + //end::collection-immutability-example[] +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/ImmutabilityMapAsBasicTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/ImmutabilityMapAsBasicTests.java new file mode 100644 index 000000000000..be2417ef8d83 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/ImmutabilityMapAsBasicTests.java @@ -0,0 +1,193 @@ +/* + * 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.orm.test.mapping.mutability.attribute; + +import java.util.Map; + +import org.hibernate.annotations.Mutability; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Property; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.spi.MappingMetamodelImplementor; +import org.hibernate.orm.test.mapping.mutability.converted.MapConverter; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.descriptor.java.Immutability; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @implNote Uses a converter just for helping with the Map part. It is the + * {@link Mutability} usage that is important + * + * @author Steve Ebersole + */ +@JiraKey( "HHH-16081" ) +@DomainModel( annotatedClasses = ImmutabilityMapAsBasicTests.TestEntity.class ) +@SessionFactory( useCollectingStatementInspector = true ) +public class ImmutabilityMapAsBasicTests { + @Test + void verifyMetamodel(DomainModelScope domainModelScope, SessionFactoryScope sessionFactoryScope) { + domainModelScope.withHierarchy( TestEntity.class, (entity) -> { + final Property property = entity.getProperty( "data" ); + assertThat( property.isUpdateable() ).isTrue(); + + final BasicValue value = (BasicValue) property.getValue(); + final BasicValue.Resolution resolution = value.resolve(); + assertThat( resolution.getMutabilityPlan().isMutable() ).isFalse(); + } ); + + final MappingMetamodelImplementor mappingMetamodel = sessionFactoryScope.getSessionFactory() + .getRuntimeMetamodels() + .getMappingMetamodel(); + final EntityPersister entityDescriptor = mappingMetamodel.getEntityDescriptor( TestEntity.class ); + final AttributeMapping attribute = entityDescriptor.findAttributeMapping( "data" ); + assertThat( attribute.getAttributeMetadata().isUpdatable() ).isTrue(); + assertThat( attribute.getExposedMutabilityPlan().isMutable() ).isFalse(); + } + + @Test + @JiraKey( "HHH-16132" ) + void testDirtyCheckingManaged(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // mutate the managed entity state - should not trigger update since it is immutable + scope.inTransaction( (session) -> { + // load a managed reference + final TestEntity managed = session.get( TestEntity.class, 1 ); + assertThat( managed.data ).hasSize( 2 ); + + // make the change + managed.data.put( "ghi", "789" ); + + // clear statements prior to flush + statementInspector.clear(); + } ); + + assertThat( statementInspector.getSqlQueries() ).isEmpty(); + } + + /** + * Illustrates how we can't really detect that an "internal state" mutation + * happened while detached. When merged, we just see a different value. + */ + @Test + @JiraKey( "HHH-16132" ) + void testDirtyCheckingMerge(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // load a detached reference + final TestEntity detached = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( detached.data ).hasSize( 2 ); + + // make the change + detached.data.put( "jkl", "007" ); + + // clear statements prior to merge + statementInspector.clear(); + + // do the merge + scope.inTransaction( (session) -> session.merge( detached ) ); + + // the SELECT + UPDATE + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + } + + @Test + @JiraKey( "HHH-16132" ) + void testNotDirtyCheckingManaged(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // make no changes to a managed entity + scope.inTransaction( (session) -> { + // Load a managed reference + final TestEntity managed = session.get( TestEntity.class, 1 ); + assertThat( managed.data ).hasSize( 2 ); + + // make no changes + + // clear statements in prep for next check + statementInspector.clear(); + } ); + + // because we made no changes, there should be no update + assertThat( statementInspector.getSqlQueries() ).isEmpty(); + } + + @Test + @JiraKey( "HHH-16132" ) + void testNotDirtyCheckingMerge(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // load a detached instance + final TestEntity detached = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( detached.data ).hasSize( 2 ); + + // clear statements in prep for next check + statementInspector.clear(); + + // merge the detached reference without making any changes + scope.inTransaction( (session) -> session.merge( detached ) ); + // (the SELECT) - should not trigger the UPDATE + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + } + + @BeforeEach + void createTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TestEntity( + 1, + CollectionHelper.toMap( + "abc", "123", + "def", "456" + ) + ) ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.createMutationQuery( "delete TestEntity" ).executeUpdate(); + } ); + } + + @Entity( name = "TestEntity" ) + @Table( name = "entity_mutable_map" ) + public static class TestEntity { + @Id + private Integer id; + + @Mutability(Immutability.class) + @Convert( converter = MapConverter.class ) + private Map data; + + private TestEntity() { + // for use by Hibernate + } + + public TestEntity(Integer id, Map data) { + this.id = id; + this.data = data; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/ImmutableMapAsBasicTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/ImmutableMapAsBasicTests.java new file mode 100644 index 000000000000..61ec7bb3ee53 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/ImmutableMapAsBasicTests.java @@ -0,0 +1,210 @@ +/* + * 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.orm.test.mapping.mutability.attribute; + +import java.util.Map; + +import org.hibernate.annotations.Immutable; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.orm.test.mapping.mutability.converted.MapConverter; +import org.hibernate.persister.entity.EntityPersister; + +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for `@Immutable` on a map-as-basic attribute + * + * Essentially the same as the `@Immutable` checks in {@link BasicAttributeMutabilityTests} in + * {@link BasicAttributeMutabilityTests#testImmutableManaged}, + * {@link BasicAttributeMutabilityTests#testImmutableDetached} and + * {@link BasicAttributeMutabilityTests#verifyDomainModel}. + * + * Again, `@Immutable` means: + * * not-updateable + * * but is oddly mutable + * + * @implNote Uses a converter just for helping with the Map part. It is the + * {@link Immutable} usage that is important + * + * @author Steve Ebersole + */ +@JiraKey( "HHH-16081" ) +@DomainModel( annotatedClasses = ImmutableMapAsBasicTests.TestEntity.class ) +@SessionFactory( useCollectingStatementInspector = true ) +public class ImmutableMapAsBasicTests { + @Test + void verifyMetamodel(DomainModelScope domainModelScope, SessionFactoryScope sessionFactoryScope) { + final PersistentClass persistentClass = domainModelScope + .getDomainModel() + .getEntityBinding( TestEntity.class.getName() ); + final EntityPersister entityDescriptor = sessionFactoryScope + .getSessionFactory() + .getRuntimeMetamodels() + .getMappingMetamodel() + .getEntityDescriptor( TestEntity.class ); + + final Property property = persistentClass.getProperty( "data" ); + assertThat( property.isUpdateable() ).isFalse(); + + final BasicValue value = (BasicValue) property.getValue(); + final BasicValue.Resolution resolution = value.resolve(); + assertThat( resolution.getMutabilityPlan().isMutable() ).isFalse(); + + final AttributeMapping attribute = entityDescriptor.findAttributeMapping( "data" ); + assertThat( attribute.getAttributeMetadata().isUpdatable() ).isFalse(); + assertThat( attribute.getExposedMutabilityPlan().isMutable() ).isFalse(); + } + + /** + * Because `@Immutable` implies non-updateable, changes are ignored (no UPDATE) + */ + @Test + @JiraKey( "HHH-16132" ) + void testDirtyCheckingManaged(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // mutate the managed entity state + scope.inTransaction( (session) -> { + // load a managed reference + final TestEntity managed = session.get( TestEntity.class, 1 ); + assertThat( managed.data ).hasSize( 2 ); + + // make the change + managed.data.put( "ghi", "789" ); + + // clear statements prior to flush + statementInspector.clear(); + } ); + + // there should be no UPDATE + assertThat( statementInspector.getSqlQueries() ).isEmpty(); + } + + /** + * Because `@Immutable` implies non-updateable, changes are ignored (no UPDATE) + */ + @Test + @JiraKey( "HHH-16132" ) + void testDirtyCheckingMerge(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // load a detached reference + final TestEntity detached = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( detached.data ).hasSize( 2 ); + + // make the change + detached.data.put( "jkl", "007" ); + + // clear statements prior to merge + statementInspector.clear(); + + // do the merge + scope.inTransaction( (session) -> session.merge( detached ) ); + + // the SELECT - no UPDATE + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( "update" ); + } + + @Test + @JiraKey( "HHH-16132" ) + void testNotDirtyCheckingManaged(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // make no changes to a managed entity + scope.inTransaction( (session) -> { + // Load a managed reference + final TestEntity managed = session.get( TestEntity.class, 1 ); + assertThat( managed.data ).hasSize( 2 ); + + // make no changes + + // clear statements in prep for next check + statementInspector.clear(); + } ); + + // because we made no changes, there should be no update + assertThat( statementInspector.getSqlQueries() ).isEmpty(); + } + + @Test + @JiraKey( "HHH-16132" ) + void testNotDirtyCheckingMerge(SessionFactoryScope scope) { + final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); + + // load a detached instance + final TestEntity detached = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); + assertThat( detached.data ).hasSize( 2 ); + + // clear statements in prep for next check + statementInspector.clear(); + + // merge the detached reference without making any changes + scope.inTransaction( (session) -> session.merge( detached ) ); + // (the SELECT) - should not trigger the UPDATE + assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); + } + + @BeforeEach + void createTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TestEntity( + 1, + CollectionHelper.toMap( + "abc", "123", + "def", "456" + ) + ) ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.createMutationQuery( "delete TestEntity" ).executeUpdate(); + } ); + } + + @Entity( name = "TestEntity" ) + @Table( name = "entity_mutable_map" ) + public static class TestEntity { + @Id + private Integer id; + + @Immutable + @Convert( converter = MapConverter.class ) + private Map data; + + private TestEntity() { + // for use by Hibernate + } + + public TestEntity(Integer id, Map data) { + this.id = id; + this.data = data; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapMutableTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/MutableMapAsBasicTests.java similarity index 57% rename from hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapMutableTests.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/MutableMapAsBasicTests.java index 22cc1f6e934f..c03187ee8ef2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/converted/converter/mutabiity/ConvertedMapMutableTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/MutableMapAsBasicTests.java @@ -4,12 +4,12 @@ * 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.orm.test.mapping.converted.converter.mutabiity; +package org.hibernate.orm.test.mapping.mutability.attribute; import java.util.Map; -import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.orm.test.mapping.mutability.converted.MapConverter; import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; @@ -20,8 +20,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import jakarta.persistence.AttributeConverter; -import jakarta.persistence.Column; import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -33,41 +31,9 @@ * @author Steve Ebersole */ @JiraKey( "HHH-16081" ) -@DomainModel( annotatedClasses = ConvertedMapMutableTests.TestEntity.class ) +@DomainModel( annotatedClasses = MutableMapAsBasicTests.TestEntity.class ) @SessionFactory( useCollectingStatementInspector = true ) -public class ConvertedMapMutableTests { - - @Test - void testMutableMap(SessionFactoryScope scope) { - final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); - - scope.inTransaction( (session) -> { - final TestEntity loaded = session.get( TestEntity.class, 1 ); - assertThat( loaded.values ).hasSize( 2 ); - loaded.values.put( "ghi", "789" ); - statementInspector.clear(); - } ); - assertThat( statementInspector.getSqlQueries() ).isNotEmpty(); - - scope.inTransaction( (session) -> { - final TestEntity loaded = session.get( TestEntity.class, 1 ); - assertThat( loaded.values ).hasSize( 3 ); - statementInspector.clear(); - } ); - assertThat( statementInspector.getSqlQueries() ).isEmpty(); - } - - @Test - void testMutableMapWithMerge(SessionFactoryScope scope) { - final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); - assertThat( loaded.values ).hasSize( 2 ); - - loaded.values.put( "ghi", "789" ); - scope.inTransaction( (session) -> session.merge( loaded ) ); - - final TestEntity changed = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); - assertThat( changed.values ).hasSize( 3 ); - } +public class MutableMapAsBasicTests { @Test @JiraKey( "HHH-16132" ) @@ -78,17 +44,17 @@ void testDirtyChecking(SessionFactoryScope scope) { scope.inTransaction( (session) -> { final TestEntity managed = session.get( TestEntity.class, 1 ); statementInspector.clear(); - assertThat( managed.values ).hasSize( 2 ); + assertThat( managed.data ).hasSize( 2 ); // make the change - managed.values.put( "ghi", "789" ); + managed.data.put( "ghi", "789" ); } ); assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); // make changes to a detached entity and merge it - should trigger update final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); - assertThat( loaded.values ).hasSize( 3 ); + assertThat( loaded.data ).hasSize( 3 ); // make the change - loaded.values.put( "jkl", "007" ); + loaded.data.put( "jkl", "007" ); statementInspector.clear(); scope.inTransaction( (session) -> session.merge( loaded ) ); // the SELECT + UPDATE @@ -104,13 +70,13 @@ void testNotDirtyChecking(SessionFactoryScope scope) { scope.inTransaction( (session) -> { final TestEntity managed = session.get( TestEntity.class, 1 ); statementInspector.clear(); - assertThat( managed.values ).hasSize( 2 ); + assertThat( managed.data ).hasSize( 2 ); } ); assertThat( statementInspector.getSqlQueries() ).isEmpty(); // make no changes to a detached entity and merge it - should not trigger update final TestEntity loaded = scope.fromTransaction( (session) -> session.get( TestEntity.class, 1 ) ); - assertThat( loaded.values ).hasSize( 2 ); + assertThat( loaded.data ).hasSize( 2 ); statementInspector.clear(); scope.inTransaction( (session) -> session.merge( loaded ) ); // the SELECT @@ -137,43 +103,22 @@ void dropTestData(SessionFactoryScope scope) { } ); } - public static class MapConverter implements AttributeConverter,String> { - @Override - public String convertToDatabaseColumn(Map map) { - if ( CollectionHelper.isEmpty( map ) ) { - return null; - } - return StringHelper.join( ", ", CollectionHelper.asPairs( map ) ); - } - - @Override - public Map convertToEntityAttribute(String pairs) { - if ( StringHelper.isEmpty( pairs ) ) { - return null; - } - return CollectionHelper.toMap( StringHelper.split( ", ", pairs ) ); - } - } - @Entity( name = "TestEntity" ) @Table( name = "entity_mutable_map" ) public static class TestEntity { - @Id - private Integer id; + @Id + private Integer id; @Convert( converter = MapConverter.class ) - @Column( name = "vals" ) - private Map values; + private Map data; private TestEntity() { // for use by Hibernate } - public TestEntity( - Integer id, - Map values) { + public TestEntity(Integer id, Map data) { this.id = id; - this.values = values; + this.data = data; } } } diff --git a/documentation/src/test/java/org/hibernate/userguide/immutability/CollectionImmutabilityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/PluralAttributeMutabilityTest.java similarity index 66% rename from documentation/src/test/java/org/hibernate/userguide/immutability/CollectionImmutabilityTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/PluralAttributeMutabilityTest.java index 9cda8af61fbe..84b260fa128a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/immutability/CollectionImmutabilityTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/PluralAttributeMutabilityTest.java @@ -2,42 +2,43 @@ * 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 . + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. */ -package org.hibernate.userguide.immutability; +package org.hibernate.orm.test.mapping.mutability.attribute; import java.util.ArrayList; import java.util.Date; import java.util.List; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; import org.hibernate.annotations.Immutable; -import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; -import org.junit.Test; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import org.jboss.logging.Logger; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; /** * @author Vlad Mihalcea */ -public class CollectionImmutabilityTest extends BaseEntityManagerFunctionalTestCase { - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { - Batch.class, - Event.class - }; - } +@DomainModel( annotatedClasses = { + PluralAttributeMutabilityTest.Batch.class, + PluralAttributeMutabilityTest.Event.class +} ) +@SessionFactory +public class PluralAttributeMutabilityTest { + private static final Logger log = Logger.getLogger( PluralAttributeMutabilityTest.class ); @Test - public void test() { - //tag::collection-immutability-persist-example[] - doInJPA(this::entityManagerFactory, entityManager -> { + public void test(SessionFactoryScope scope) { + scope.inTransaction( (entityManager) -> { + //tag::collection-immutability-persist-example[] Batch batch = new Batch(); batch.setId(1L); batch.setName("Change request"); @@ -56,21 +57,27 @@ public void test() { batch.getEvents().add(event2); entityManager.persist(batch); - }); - //end::collection-immutability-persist-example[] - //tag::collection-entity-update-example[] - doInJPA(this::entityManagerFactory, entityManager -> { + //end::collection-immutability-persist-example[] + } ); + + scope.inTransaction( (entityManager) -> { + //tag::collection-entity-update-example[] Batch batch = entityManager.find(Batch.class, 1L); log.info("Change batch name"); batch.setName("Proposed change request"); - }); - //end::collection-entity-update-example[] + //end::collection-entity-update-example[] + } ); + //tag::collection-immutability-update-example[] try { - doInJPA(this::entityManagerFactory, entityManager -> { - Batch batch = entityManager.find(Batch.class, 1L); + //end::collection-immutability-update-example[] + scope.inTransaction( (entityManager) -> { + //tag::collection-immutability-update-example[] + Batch batch = entityManager.find( Batch.class, 1L ); batch.getEvents().clear(); - }); + //end::collection-immutability-update-example[] + } ); + //tag::collection-immutability-update-example[] } catch (Exception e) { log.error("Immutable collections cannot be modified"); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/DateConverter.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/DateConverter.java new file mode 100644 index 000000000000..806a9c8366be --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/DateConverter.java @@ -0,0 +1,38 @@ +/* + * 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.orm.test.mapping.mutability.converted; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +import org.hibernate.internal.util.StringHelper; + +import jakarta.persistence.AttributeConverter; + +/** + * Handles Date as a character data on the database + * + * @author Steve Ebersole + */ +public class DateConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(Date date) { + if ( date == null ) { + return null; + } + return DateTimeFormatter.ISO_INSTANT.format( date.toInstant() ); + } + + @Override + public Date convertToEntityAttribute(String date) { + if ( StringHelper.isEmpty( date ) ) { + return null; + } + return Date.from( Instant.from( DateTimeFormatter.ISO_INSTANT.parse( date ) ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityConverterTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityConverterTests.java new file mode 100644 index 000000000000..754b7a98793b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityConverterTests.java @@ -0,0 +1,85 @@ +/* + * 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.orm.test.mapping.mutability.converted; + +import java.util.Date; + +import org.hibernate.annotations.Mutability; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Property; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests applying {@link Mutability} to an {@link jakarta.persistence.AttributeConverter}. + * + * Here we just verify that has the same effect as {@link Mutability} directly on the attribute + * in terms of configuring the boot model references. + * + * @see ImmutableConvertedBaselineTests + * + * @author Steve Ebersole + */ +@ServiceRegistry +@DomainModel( annotatedClasses = ImmutabilityConverterTests.TestEntity.class ) +public class ImmutabilityConverterTests { + @Test + void verifyMetamodel(DomainModelScope scope) { + scope.withHierarchy( TestEntity.class, (entity) -> { + final Property theDateProperty = entity.getProperty( "theDate" ); + assertThat( theDateProperty ).isNotNull(); + assertThat( theDateProperty.isUpdateable() ).isTrue(); + + final BasicValue basicValue = (BasicValue) theDateProperty.getValue(); + final BasicValue.Resolution resolution = basicValue.resolve(); + assertThat( resolution.getMutabilityPlan().isMutable() ).isFalse(); + } ); + } + + @Entity( name = "TestEntity" ) + @Table( name = "TestEntity" ) + public static class TestEntity { + @Id + private Integer id; + @Basic + private String name; + @Convert( converter = ImmutabilityDateConverter.class ) + private Date theDate; + + private TestEntity() { + // for use by Hibernate + } + + public TestEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityDateConverter.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityDateConverter.java new file mode 100644 index 000000000000..4b67f4da43da --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityDateConverter.java @@ -0,0 +1,17 @@ +/* + * 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.orm.test.mapping.mutability.converted; + +import org.hibernate.annotations.Mutability; +import org.hibernate.type.descriptor.java.Immutability; + +/** + * @author Steve Ebersole + */ +@Mutability(Immutability.class) +public class ImmutabilityDateConverter extends DateConverter { +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityMapConverter.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityMapConverter.java new file mode 100644 index 000000000000..a44fa232dfe2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutabilityMapConverter.java @@ -0,0 +1,17 @@ +/* + * 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.orm.test.mapping.mutability.converted; + +import org.hibernate.annotations.Mutability; +import org.hibernate.type.descriptor.java.Immutability; + +/** + * @author Steve Ebersole + */ +@Mutability(Immutability.class) +public class ImmutabilityMapConverter extends MapConverter { +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableConvertedBaselineTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableConvertedBaselineTests.java new file mode 100644 index 000000000000..5c92c3ec895e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableConvertedBaselineTests.java @@ -0,0 +1,226 @@ +/* + * 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.orm.test.mapping.mutability.converted; + +import java.time.Instant; +import java.util.Date; + +import org.hibernate.annotations.Immutable; +import org.hibernate.annotations.Mutability; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Property; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.orm.test.mapping.mutability.attribute.BasicAttributeMutabilityTests; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.descriptor.java.Immutability; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for combining {@link Mutability} and {@link Immutable} with conversions + * directly on attributes as a baseline for applying {@link Mutability} and {@link Immutable} + * to the converter class + * + * @see BasicAttributeMutabilityTests + * @see ImmutableConverterTests + * @see ImmutabilityConverterTests + * + * @author Steve Ebersole + */ +@DomainModel( annotatedClasses = ImmutableConvertedBaselineTests.TheEntity.class ) +@SessionFactory +public class ImmutableConvertedBaselineTests { + private static final Instant START = Instant.now(); + + /** + * Essentially the same as {@link BasicAttributeMutabilityTests#verifyDomainModel} + */ + @Test + void verifyDomainModel(DomainModelScope domainModelScope, SessionFactoryScope sfSessionFactoryScope) { + final PersistentClass persistentClass = domainModelScope.getEntityBinding( TheEntity.class ); + final EntityPersister entityDescriptor = sfSessionFactoryScope + .getSessionFactory() + .getRuntimeMetamodels() + .getMappingMetamodel() + .getEntityDescriptor( TheEntity.class ); + + // `@Immutable` + final Property theDateProperty = persistentClass.getProperty( "theDate" ); + assertThat( theDateProperty.isUpdateable() ).isFalse(); + final AttributeMapping theDateAttribute = entityDescriptor.findAttributeMapping( "theDate" ); + assertThat( theDateAttribute.getExposedMutabilityPlan().isMutable() ).isFalse(); + + // `@Mutability(Immutability.class)` + final Property anotherDateProperty = persistentClass.getProperty( "anotherDate" ); + assertThat( anotherDateProperty.isUpdateable() ).isTrue(); + final AttributeMapping anotherDateAttribute = entityDescriptor.findAttributeMapping( "anotherDate" ); + assertThat( anotherDateAttribute.getExposedMutabilityPlan().isMutable() ).isFalse(); + } + + /** + * Effectively the same as {@linkplain BasicAttributeMutabilityTests#testImmutableManaged} + * + * Because it is non-updateable, no UPDATE + */ + @Test + void testImmutableManaged(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "@Immutable test", Date.from( START ) ) ); + } ); + + // load a managed reference and mutate the date + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + theEntity.theDate.setTime( Instant.EPOCH.toEpochMilli() ); + } ); + + // reload the entity and verify that the mutation was ignored + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.theDate.getTime() ).isEqualTo( START.toEpochMilli() ); + } ); + } + + /** + * Effectively the same as {@linkplain BasicAttributeMutabilityTests#testImmutableDetached} + * + * Because it is non-updateable, no UPDATE + */ + @Test + void testImmutableMerge(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "@Immutable test", Date.from( START ) ) ); + } ); + + // load a detached reference + final TheEntity detached = scope.fromTransaction( (session) -> session.find( TheEntity.class, 1 ) ); + + // make the change to the detached instance and merge it + detached.theDate.setTime( Instant.EPOCH.toEpochMilli() ); + scope.inTransaction( (session) -> session.merge( detached ) ); + + // verify the value did not change + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.theDate.getTime() ).isEqualTo( START.toEpochMilli() ); + } ); + } + + /** + * Effectively the same as {@linkplain BasicAttributeMutabilityTests#testImmutabilityManaged}. + * + * Because the state mutation is done on a managed instance, Hibernate detects that; and + * because it is internal-state-immutable, we will ignore the mutation and there will + * be no UPDATE + */ + @Test + void testImmutabilityManaged(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "@Mutability test", Date.from( START ) ) ); + } ); + + // try to update the managed form + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.anotherDate ).isEqualTo( Date.from( START ) ); + theEntity.anotherDate.setTime( Instant.EPOCH.toEpochMilli() ); + } ); + + // reload it and verify the value did not change + final TheEntity detached = scope.fromTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.anotherDate ).isEqualTo( Date.from( START ) ); + return theEntity; + } ); + + // Unfortunately, dues to how merge works (find + set) this change to the + // detached instance looks like a set when applied to the managed instance. + // Therefore, from the perspective of the merge operation, the Date itself was + // set rather than its internal state being changed. AKA, this will "correctly" + // result in an update + detached.anotherDate.setTime( Instant.EPOCH.toEpochMilli() ); + scope.inTransaction( (session) -> session.merge( detached ) ); + + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.anotherDate ).isEqualTo( Date.from( Instant.EPOCH ) ); + } ); + } + + /** + * Effectively the same as {@linkplain BasicAttributeMutabilityTests#testImmutabilityDetached} + * + * There will be an UPDATE because we cannot distinguish one type of change from another while detached + */ + @Test + void testImmutabilityDetached(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.persist( new TheEntity( 1, "@Mutability test", Date.from( START ) ) ); + } ); + + // load a detached reference + final TheEntity detached = scope.fromTransaction( (session) -> session.find( TheEntity.class, 1 ) ); + + // mutate the date and merge the detached ref + detached.anotherDate.setTime( Instant.EPOCH.toEpochMilli() ); + scope.inTransaction( (session) -> session.merge( detached ) ); + + // verify the value change was persisted + scope.inTransaction( (session) -> { + final TheEntity theEntity = session.find( TheEntity.class, 1 ); + assertThat( theEntity.anotherDate.getTime() ).isEqualTo( Instant.EPOCH.toEpochMilli() ); + } ); + } + + @AfterEach + void dropTestData(SessionFactoryScope scope) { + scope.inTransaction( (session) -> session.createMutationQuery( "delete TheEntity" ).executeUpdate() ); + } + + @Entity( name = "TheEntity" ) + @Table( name = "TheEntity" ) + public static class TheEntity { + @Id + private Integer id; + + @Basic + private String name; + + @Immutable + @Convert(converter = DateConverter.class) + private Date theDate; + + @Mutability(Immutability.class) + @Convert(converter = DateConverter.class) + private Date anotherDate; + + + private TheEntity() { + // for use by Hibernate + } + + public TheEntity(Integer id, String name, Date aDate) { + this.id = id; + this.name = name; + this.theDate = aDate; + this.anotherDate = aDate; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableConverterTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableConverterTests.java new file mode 100644 index 000000000000..9f1014524aea --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableConverterTests.java @@ -0,0 +1,100 @@ +/* + * 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.orm.test.mapping.mutability.converted; + +import java.util.Date; + +import org.hibernate.annotations.Immutable; +import org.hibernate.mapping.BasicValue; +import org.hibernate.mapping.Property; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests applying {@link Immutable} to an {@link jakarta.persistence.AttributeConverter}. + * + * Here we just verify that has the same effect as {@link Immutable} directly on the attribute + * in terms of configuring the boot model references. + * + * @see ImmutableConvertedBaselineTests + * + * @author Steve Ebersole + */ +@ServiceRegistry +@DomainModel( annotatedClasses = ImmutableConverterTests.TestEntity.class ) +@SessionFactory +public class ImmutableConverterTests { + @Test + void verifyMetamodel(DomainModelScope scope) { + scope.withHierarchy( TestEntity.class, (entity) -> { + { + final Property property = entity.getProperty( "mutableDate" ); + assertThat( property ).isNotNull(); + assertThat( property.isUpdateable() ).isTrue(); + + final BasicValue basicValue = (BasicValue) property.getValue(); + final BasicValue.Resolution resolution = basicValue.resolve(); + assertThat( resolution.getMutabilityPlan().isMutable() ).isTrue(); + } + + { + final Property property = entity.getProperty( "immutableDate" ); + assertThat( property ).isNotNull(); + assertThat( property.isUpdateable() ).isTrue(); + + final BasicValue basicValue = (BasicValue) property.getValue(); + final BasicValue.Resolution resolution = basicValue.resolve(); + assertThat( resolution.getMutabilityPlan().isMutable() ).isFalse(); + } + } ); + } + + @Entity( name = "TestEntity" ) + @Table( name = "TestEntity" ) + public static class TestEntity { + @Id + private Integer id; + @Basic + private String name; + @Convert( converter = ImmutableDateConverter.class ) + private Date immutableDate; + private Date mutableDate; + + private TestEntity() { + // for use by Hibernate + } + + public TestEntity(Integer id, String name) { + this.id = id; + this.name = name; + } + + public Integer getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableDateConverter.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableDateConverter.java new file mode 100644 index 000000000000..fdb364215d62 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableDateConverter.java @@ -0,0 +1,16 @@ +/* + * 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.orm.test.mapping.mutability.converted; + +import org.hibernate.annotations.Immutable; + +/** + * @author Steve Ebersole + */ +@Immutable +public class ImmutableDateConverter extends DateConverter { +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableMapConverter.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableMapConverter.java new file mode 100644 index 000000000000..b6a4401a6a26 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/ImmutableMapConverter.java @@ -0,0 +1,16 @@ +/* + * 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.orm.test.mapping.mutability.converted; + +import org.hibernate.annotations.Immutable; + +/** + * @author Steve Ebersole + */ +@Immutable +public class ImmutableMapConverter extends MapConverter { +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/MapConverter.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/MapConverter.java new file mode 100644 index 000000000000..a01dffb89de1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/converted/MapConverter.java @@ -0,0 +1,35 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html. + */ +package org.hibernate.orm.test.mapping.mutability.converted; + +import java.util.Map; + +import org.hibernate.internal.util.StringHelper; +import org.hibernate.internal.util.collections.CollectionHelper; + +import jakarta.persistence.AttributeConverter; + +/** + * @author Steve Ebersole + */ +public class MapConverter implements AttributeConverter, String> { + @Override + public String convertToDatabaseColumn(Map map) { + if ( CollectionHelper.isEmpty( map ) ) { + return null; + } + return StringHelper.join( ", ", CollectionHelper.asPairs( map ) ); + } + + @Override + public Map convertToEntityAttribute(String pairs) { + if ( StringHelper.isEmpty( pairs ) ) { + return null; + } + return CollectionHelper.toMap( StringHelper.split( ", ", pairs ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/EntityImmutabilityTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/EntityImmutabilityTest.java new file mode 100644 index 000000000000..a7cd47409cc5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/EntityImmutabilityTest.java @@ -0,0 +1,113 @@ +/* + * 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.orm.test.mapping.mutability.entity; + +import java.util.Date; + +import org.hibernate.annotations.Immutable; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.Test; + +import org.jboss.logging.Logger; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Vlad Mihalcea + */ +@DomainModel( annotatedClasses = EntityImmutabilityTest.Event.class ) +@SessionFactory +public class EntityImmutabilityTest { + private static final Logger log = Logger.getLogger( EntityImmutabilityTest.class ); + + @Test + void verifyMetamodel(DomainModelScope scope) { + scope.withHierarchy( Event.class, (entity) -> { + assertThat( entity.isMutable() ).isFalse(); + + // this implies that all attributes and mapped columns are non-updateable, + // but the code does not explicitly set that. The functional test + // verifies that they function as non-updateable + } ); + } + + @Test + public void test(SessionFactoryScope scope) { + scope.inTransaction( (entityManager) -> { + //tag::entity-immutability-persist-example[] + Event event = new Event(); + event.setId(1L); + event.setCreatedOn(new Date()); + event.setMessage("Hibernate User Guide rocks!"); + + entityManager.persist(event); + //end::entity-immutability-persist-example[] + } ); + + scope.inTransaction( (entityManager) -> { + //tag::entity-immutability-update-example[] + Event event = entityManager.find(Event.class, 1L); + log.info("Change event message"); + event.setMessage("Hibernate User Guide"); + //end::entity-immutability-update-example[] + } ); + scope.inTransaction( (entityManager) -> { + Event event = entityManager.find(Event.class, 1L); + assertThat( event.getMessage() ).isEqualTo( "Hibernate User Guide rocks!" ); + } ); + } + + //tag::entity-immutability-example[] + @Entity(name = "Event") + @Immutable + public static class Event { + + @Id + private Long id; + + private Date createdOn; + + private String message; + + //Getters and setters are omitted for brevity + + //end::entity-immutability-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Date getCreatedOn() { + return createdOn; + } + + public void setCreatedOn(Date createdOn) { + this.createdOn = createdOn; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + //tag::entity-immutability-example[] + } + //end::entity-immutability-example[] +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/EntityMutabilityPlanTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/EntityMutabilityPlanTest.java new file mode 100644 index 000000000000..837645ab1583 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/EntityMutabilityPlanTest.java @@ -0,0 +1,50 @@ +/* + * 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.orm.test.mapping.mutability.entity; + +import java.util.Date; + +import org.hibernate.MappingException; +import org.hibernate.annotations.Mutability; +import org.hibernate.boot.MetadataSources; +import org.hibernate.type.descriptor.java.Immutability; + +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.ServiceRegistryScope; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author Steve Ebersole + */ +@ServiceRegistry +public class EntityMutabilityPlanTest { + @Test + void verifyMetamodel(ServiceRegistryScope scope) { + final MetadataSources metadataSources = new MetadataSources( scope.getRegistry() ); + metadataSources.addAnnotatedClass( Event.class ); + try { + metadataSources.buildMetadata(); + fail( "Expecting exception about @Mutability on the entity" ); + } + catch (MappingException expected) { + } + } + + @Entity(name = "Event") + @Mutability(Immutability.class) + public static class Event { + @Id + private Long id; + private Date createdOn; + private String message; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/package-info.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/package-info.java new file mode 100644 index 000000000000..ca67149b5243 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/entity/package-info.java @@ -0,0 +1,13 @@ +/* + * 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. + */ + +/** + * Tests for the behavior of {@link org.hibernate.annotations.Immutable} attached to an entity + * + * @author Steve Ebersole + */ +package org.hibernate.orm.test.mapping.mutability.entity; diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java index 34211eddf16e..dafcf2ac301a 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/DomainModelScope.java @@ -50,6 +50,11 @@ default void withHierarchy(String rootTypeName, Consumer action) { action.accept( entityBinding.getRootClass() ); } + default PersistentClass getEntityBinding(Class theEntityClass) { + assert theEntityClass != null; + return getDomainModel().getEntityBinding( theEntityClass.getName() ); + } + // ... } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java index a3ef58448bbf..272d799bf5e9 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryScope.java @@ -27,6 +27,10 @@ public interface SessionFactoryScope { T getStatementInspector(Class type); SQLStatementInspector getCollectingStatementInspector(); + default void withSessionFactory(Consumer action) { + action.accept( getSessionFactory() ); + } + void inSession(Consumer action); void inTransaction(Consumer action); void inTransaction(SessionImplementor session, Consumer action); From 4c99dbb75c250f1127bfb8fc008794ac0e3b5641 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 10 Feb 2023 11:49:36 +0100 Subject: [PATCH 0065/1497] Get rid of capturing lambdas for Bindable#JdbcValueConsumer --- .../AbstractCompositeIdentifierMapping.java | 10 +- .../hibernate/metamodel/mapping/Bindable.java | 100 ++++++++++++++++-- .../mapping/EmbeddableValuedModelPart.java | 16 ++- .../mapping/EntityValuedModelPart.java | 16 +-- .../AbstractDiscriminatorMapping.java | 8 +- .../internal/AnyDiscriminatorPart.java | 6 +- .../mapping/internal/AnyKeyPart.java | 16 +-- .../internal/BasicAttributeMapping.java | 8 +- .../BasicEntityIdentifierMappingImpl.java | 8 +- .../internal/BasicValuedCollectionPart.java | 8 +- .../CollectionIdentifierDescriptorImpl.java | 8 +- .../internal/CompoundNaturalIdMapping.java | 16 +-- ...criminatedAssociationAttributeMapping.java | 14 ++- .../internal/DiscriminatedCollectionPart.java | 8 +- .../internal/EmbeddableMappingTypeImpl.java | 16 +-- .../EmbeddedForeignKeyDescriptor.java | 8 +- .../EmbeddedIdentifierMappingImpl.java | 8 +- .../internal/EntityRowIdMappingImpl.java | 8 +- .../internal/EntityVersionMappingImpl.java | 8 +- .../mapping/internal/IdClassEmbeddable.java | 14 ++- ...InverseNonAggregatedIdentifierMapping.java | 8 +- .../NonAggregatedIdentifierMappingImpl.java | 6 +- .../internal/PluralAttributeMappingImpl.java | 8 +- .../internal/SimpleForeignKeyDescriptor.java | 16 +-- .../internal/SimpleNaturalIdMapping.java | 16 +-- .../internal/ToOneAttributeMapping.java | 16 ++- .../mapping/internal/VirtualIdEmbeddable.java | 16 +-- .../model/domain/internal/ArrayTupleType.java | 6 +- .../TupleMappingModelExpressible.java | 16 +-- .../entity/AbstractEntityPersister.java | 16 +-- .../entity/mutation/EntityTableMapping.java | 6 +- .../AnonymousTupleBasicValuedModelPart.java | 16 +-- ...onymousTupleEmbeddableValuedModelPart.java | 16 +-- .../AnonymousTupleEntityValuedModelPart.java | 16 +-- .../AnonymousTupleTableGroupProducer.java | 8 +- .../sqm/sql/BaseSqmToSqlAstConverter.java | 13 ++- .../tree/expression/EntityTypeLiteral.java | 16 +-- .../sql/ast/tree/expression/JdbcLiteral.java | 16 +-- .../exec/internal/AbstractJdbcParameter.java | 16 +-- .../sql/exec/spi/JdbcParameterBindings.java | 8 +- .../java/org/hibernate/type/BasicType.java | 8 +- .../GoofyPersisterClassProvider.java | 6 +- .../PersisterClassProviderTest.java | 6 +- .../orm/test/legacy/CustomPersister.java | 6 +- 44 files changed, 401 insertions(+), 184 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractCompositeIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractCompositeIdentifierMapping.java index 7a42edd56b54..71551e0b45c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractCompositeIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/internal/AbstractCompositeIdentifierMapping.java @@ -187,10 +187,12 @@ public void visitSubParts( } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { int span = 0; final EmbeddableMappingType embeddableTypeDescriptor = getEmbeddableTypeDescriptor(); @@ -209,12 +211,14 @@ public int forEachJdbcValue( span += fkDescriptor.forEachJdbcValue( identifier, span + offset, + x, + y, valuesConsumer, session ); } else { - span += attributeMapping.forEachJdbcValue( o, span + offset, valuesConsumer, session ); + span += attributeMapping.forEachJdbcValue( o, span + offset, x, y, valuesConsumer, session ); } } return span; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Bindable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Bindable.java index 7be45fecbf9e..82f04f6a8f19 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Bindable.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Bindable.java @@ -111,51 +111,131 @@ default int forEachJdbcType(IndexedConsumer action) { * consumer.consume( 28 ); * ```` * - * Think of it as breaking the multi-dimensional array into a visitable flat array + * Think of it as breaking the multi-dimensional array into a visitable flat array. + * Additionally, it passes through the values {@code X} and {@code Y} to the consumer. + */ + default int forEachDisassembledJdbcValue( + Object value, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return forEachDisassembledJdbcValue( value, 0, x, y, valuesConsumer, session ); + } + + /** + * Like {@link #forEachDisassembledJdbcValue(Object, Object, Object, JdbcValuesBiConsumer, SharedSessionContractImplementor)}, + * but additionally receives an offset by which the selectionIndex is incremented when calling {@link JdbcValuesBiConsumer#consume(int, Object, Object, Object, JdbcMapping)}. + */ + int forEachDisassembledJdbcValue( + Object value, + int offset, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session); + + /** + * A short hand form of {@link #forEachDisassembledJdbcValue(Object, Object, Object, JdbcValuesBiConsumer, SharedSessionContractImplementor)}, + * that passes null for the two values {@code X} and {@code Y}. */ default int forEachDisassembledJdbcValue( Object value, JdbcValuesConsumer valuesConsumer, SharedSessionContractImplementor session) { - return forEachDisassembledJdbcValue( value, 0, valuesConsumer, session ); + return forEachDisassembledJdbcValue( value, null, null, valuesConsumer, session ); } - int forEachDisassembledJdbcValue( + /** + * A short hand form of {@link #forEachDisassembledJdbcValue(Object, int, Object, Object, JdbcValuesBiConsumer, SharedSessionContractImplementor)}, + * that passes null for the two values {@code X} and {@code Y} . + */ + default int forEachDisassembledJdbcValue( Object value, int offset, JdbcValuesConsumer valuesConsumer, - SharedSessionContractImplementor session); + SharedSessionContractImplementor session) { + return forEachDisassembledJdbcValue( value, offset, null, null, valuesConsumer, session ); + } /** * Visit each constituent JDBC value extracted from the entity instance itself. * * Short-hand form of calling {@link #disassemble} and piping its result to - * {@link #forEachDisassembledJdbcValue} + * {@link #forEachDisassembledJdbcValue(Object, JdbcValuesConsumer, SharedSessionContractImplementor)} + */ + default int forEachJdbcValue( + Object value, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return forEachJdbcValue( value, 0, x, y, valuesConsumer, session ); + } + + /** + * Visit each constituent JDBC value extracted from the entity instance itself. + * + * Short-hand form of calling {@link #disassemble} and piping its result to + * {@link #forEachDisassembledJdbcValue(Object, int, JdbcValuesConsumer, SharedSessionContractImplementor)} + */ + default int forEachJdbcValue( + Object value, + int offset, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, + SharedSessionContractImplementor session) { + return forEachDisassembledJdbcValue( disassemble( value, session ), offset, x, y, valuesConsumer, session ); + } + + /** + * A short hand form of {@link #forEachJdbcValue(Object, Object, Object, JdbcValuesBiConsumer, SharedSessionContractImplementor)}, + * that passes null for the two values {@code X} and {@code Y}. */ default int forEachJdbcValue( Object value, JdbcValuesConsumer valuesConsumer, SharedSessionContractImplementor session) { - return forEachJdbcValue( value, 0, valuesConsumer, session ); + return forEachJdbcValue( value, null, null, valuesConsumer, session ); } + /** + * A short hand form of {@link #forEachJdbcValue(Object, int, Object, Object, JdbcValuesBiConsumer, SharedSessionContractImplementor)}, + * that passes null for the two values {@code X} and {@code Y}. + */ default int forEachJdbcValue( Object value, int offset, JdbcValuesConsumer valuesConsumer, SharedSessionContractImplementor session) { - return forEachDisassembledJdbcValue( disassemble( value, session ), offset, valuesConsumer, session ); + return forEachJdbcValue( value, offset, null, null, valuesConsumer, session ); } - /** - * Functional interface for consuming the JDBC values. Essentially a {@link java.util.function.BiConsumer} + * Functional interface for consuming the JDBC values. */ @FunctionalInterface - interface JdbcValuesConsumer { + interface JdbcValuesConsumer extends JdbcValuesBiConsumer { + @Override + default void consume(int selectionIndex, Object o, Object o2, Object jdbcValue, JdbcMapping jdbcMapping) { + consume( selectionIndex, jdbcValue, jdbcMapping ); + } + /** * Consume a JDBC-level jdbcValue. The JDBC jdbcMapping descriptor is also passed in */ void consume(int selectionIndex, Object jdbcValue, JdbcMapping jdbcMapping); } + + /** + * Functional interface for consuming the JDBC values, along with two values of type {@code X} and {@code Y}. + */ + @FunctionalInterface + interface JdbcValuesBiConsumer { + /** + * Consume a JDBC-level jdbcValue. The JDBC jdbcMapping descriptor is also passed in + */ + void consume(int selectionIndex, X x, Y y, Object jdbcValue, JdbcMapping jdbcMapping); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableValuedModelPart.java index dc2e0449e8f5..b3c1b675cb4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableValuedModelPart.java @@ -72,12 +72,14 @@ default int forEachJdbcType(int offset, IndexedConsumer action) { } @Override - default int forEachJdbcValue( + default int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return getEmbeddableTypeDescriptor().forEachJdbcValue( value, offset, valuesConsumer, session ); + return getEmbeddableTypeDescriptor().forEachJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override @@ -91,14 +93,18 @@ default int forEachSelectable(int offset, SelectableConsumer consumer) { } @Override - default int forEachDisassembledJdbcValue( + default int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { return getEmbeddableTypeDescriptor().forEachDisassembledJdbcValue( value, offset, + x, + y, valuesConsumer, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java index 777ec83ecb11..27e2850978d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityValuedModelPart.java @@ -101,20 +101,24 @@ default Object disassemble(Object value, SharedSessionContractImplementor sessio } @Override - default int forEachDisassembledJdbcValue( + default int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return getEntityMappingType().forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return getEntityMappingType().forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override - default int forEachJdbcValue( + default int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer consumer, + X x, + Y y, + JdbcValuesBiConsumer consumer, SharedSessionContractImplementor session) { - return getEntityMappingType().forEachJdbcValue( value, offset, consumer, session ); + return getEntityMappingType().forEachJdbcValue( value, offset, x, y, consumer, session ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java index c798206a5382..3ee008a0b685 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java @@ -221,12 +221,14 @@ public void applySqlSelections( } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, jdbcMapping ); + valuesConsumer.consume( offset, x, y, value, jdbcMapping ); return getJdbcTypeCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java index 4e7a77f591a3..737ada91670d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java @@ -214,10 +214,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { throw new UnsupportedOperationException(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java index 5fae701ff54e..42034e3cc532 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java @@ -270,12 +270,14 @@ public int forEachJdbcType(int offset, IndexedConsumer action) { } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, jdbcMapping ); + valuesConsumer.consume( offset, x, y, value, jdbcMapping ); return getJdbcTypeCount(); } @@ -303,12 +305,14 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, jdbcMapping ); + valuesConsumer.consume( offset, x, y, value, jdbcMapping ); return 1; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java index 545d84b32c44..a931fe567101 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java @@ -399,12 +399,14 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, getJdbcMapping() ); + valuesConsumer.consume( offset, x, y, value, getJdbcMapping() ); return getJdbcTypeCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java index 0eaef6effe60..199df6f646b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java @@ -376,12 +376,14 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return idType.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return idType.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java index 7a53d2eb8f6f..66c8eff25343 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java @@ -334,12 +334,14 @@ public void decompose(Object domainValue, JdbcValueConsumer valueConsumer, Share } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, getJdbcMapping() ); + valuesConsumer.consume( offset, x, y, value, getJdbcMapping() ); return getJdbcTypeCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java index abae1b07fede..028c561bfa62 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java @@ -317,11 +317,13 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return type.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return type.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java index 299c10b423f9..1ae9cf8dab23 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java @@ -386,10 +386,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { assert value instanceof Object[]; @@ -398,16 +400,18 @@ public int forEachDisassembledJdbcValue( int span = 0; for ( int i = 0; i < attributes.size(); i++ ) { final SingularAttributeMapping attribute = attributes.get( i ); - span += attribute.forEachDisassembledJdbcValue( incoming[ i ], span + offset, valuesConsumer, session ); + span += attribute.forEachDisassembledJdbcValue( incoming[ i ], span + offset, x, y, valuesConsumer, session ); } return span; } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { assert value instanceof Object[]; @@ -417,7 +421,7 @@ public int forEachJdbcValue( int span = 0; for ( int i = 0; i < attributes.size(); i++ ) { final SingularAttributeMapping attribute = attributes.get( i ); - span += attribute.forEachJdbcValue( incoming[ i ], span + offset, valuesConsumer, session ); + span += attribute.forEachJdbcValue( incoming[ i ], span + offset, x, y, valuesConsumer, session ); } return span; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java index c96df4a0c768..a1aa1dad9fad 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java @@ -266,21 +266,27 @@ public int forEachJdbcType(IndexedConsumer action) { } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { if ( value != null ) { if ( value.getClass().isArray() ) { final Object[] values = (Object[]) value; valuesConsumer.consume( offset, + x, + y, values[0], discriminatorMapping.getDiscriminatorPart().getJdbcMapping() ); valuesConsumer.consume( offset + 1, + x, + y, values[1], discriminatorMapping.getKeyPart().getJdbcMapping() ); @@ -294,6 +300,8 @@ public int forEachDisassembledJdbcValue( final Object disassembledDiscriminator = discriminatorMapping.getDiscriminatorPart().disassemble( discriminator, session ); valuesConsumer.consume( offset, + x, + y, disassembledDiscriminator, discriminatorMapping.getDiscriminatorPart().getJdbcMapping() ); @@ -303,6 +311,8 @@ public int forEachDisassembledJdbcValue( final Object disassembledKey = discriminatorMapping.getKeyPart().disassemble( identifier, session ); valuesConsumer.consume( offset + 1, + x, + y, disassembledKey, discriminatorMapping.getKeyPart().getJdbcMapping() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java index 0d85f00bd3c9..308902f50611 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java @@ -267,14 +267,18 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { return discriminatorMapping.getDiscriminatorPart().forEachDisassembledJdbcValue( value, offset, + x, + y, valuesConsumer, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java index 8ce82d214ef5..b828c794a6ce 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java @@ -760,10 +760,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer consumer, + X x, + Y y, + JdbcValuesBiConsumer consumer, SharedSessionContractImplementor session) { int span = 0; @@ -773,22 +775,24 @@ public int forEachJdbcValue( continue; } final Object o = attributeMapping.getPropertyAccess().getGetter().get( value ); - span += attributeMapping.forEachJdbcValue( o, span + offset, consumer, session ); + span += attributeMapping.forEachJdbcValue( o, span + offset, x, y, consumer, session ); } return span; } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { final Object[] values = (Object[]) value; int span = 0; for ( int i = 0; i < attributeMappings.size(); i++ ) { final AttributeMapping mapping = attributeMappings.get( i ); - span += mapping.forEachDisassembledJdbcValue( values[i], span + offset, valuesConsumer, session ); + span += mapping.forEachDisassembledJdbcValue( values[i], span + offset, x, y, valuesConsumer, session ); } return span; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java index c2a7055cbda0..f955a8032b68 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java @@ -586,12 +586,14 @@ public int forEachJdbcType(int offset, IndexedConsumer action) { } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return targetSide.getModelPart().forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return targetSide.getModelPart().forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java index c0081f7d5191..3e28a53b88e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java @@ -136,14 +136,18 @@ public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsu } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { return getEmbeddableTypeDescriptor().forEachDisassembledJdbcValue( value, offset, + x, + y, valuesConsumer, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java index a2a46ea2a0e7..0a4cf5372509 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java @@ -124,12 +124,14 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return rowIdType.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return rowIdType.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java index a1470f61d89c..821f08823929 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java @@ -328,12 +328,14 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return versionBasicType.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return versionBasicType.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java index 8608832f7619..e4d4dac215e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java @@ -405,10 +405,12 @@ public JdbcMapping getJdbcMapping(int index) { } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { int span = 0; @@ -418,7 +420,7 @@ public int forEachJdbcValue( continue; } final Object o = attributeMapping.getPropertyAccess().getGetter().get( value ); - span += attributeMapping.forEachJdbcValue( o, span + offset, valuesConsumer, session ); + span += attributeMapping.forEachJdbcValue( o, span + offset, x, y, valuesConsumer, session ); } return span; } @@ -437,10 +439,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { throw new UnsupportedOperationException(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java index ca0db141bf70..79813f3533f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java @@ -134,12 +134,14 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return identifierValueMapper.forEachJdbcValue( value, offset, valuesConsumer, session ); + return identifierValueMapper.forEachJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java index 8425cfaa43df..3a55c2011cf0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java @@ -148,12 +148,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, Y y, JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return identifierValueMapper.forEachJdbcValue( value, offset, valuesConsumer, session ); + return identifierValueMapper.forEachJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java index 98afb6c66755..b0c4f6c5a2a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java @@ -950,12 +950,14 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return elementDescriptor.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return elementDescriptor.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java index 73744c492bfd..574adce8d395 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java @@ -454,12 +454,14 @@ public Object getAssociationKeyFromSide( } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, getJdbcMapping() ); + valuesConsumer.consume( offset, x, y, value, getJdbcMapping() ); return 1; } @@ -519,12 +521,14 @@ public int forEachJdbcType(int offset, IndexedConsumer action) { } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, disassemble( value, session ), targetSide.getModelPart().getJdbcMapping() ); + valuesConsumer.consume( offset, x, y, disassemble( value, session ), targetSide.getModelPart().getJdbcMapping() ); return getJdbcTypeCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java index 28645c333a6f..aa835d44f069 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java @@ -248,21 +248,25 @@ public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsu } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return attribute.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return attribute.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return attribute.forEachJdbcValue( value, offset, valuesConsumer, session ); + return attribute.forEachJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java index 7a3e40b8b394..624001f46d9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java @@ -2148,19 +2148,23 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return foreignKeyDescriptor.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return foreignKeyDescriptor.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer consumer, + X x, + Y y, + JdbcValuesBiConsumer consumer, SharedSessionContractImplementor session) { return foreignKeyDescriptor.forEachDisassembledJdbcValue( foreignKeyDescriptor.disassemble( @@ -2168,6 +2172,8 @@ public int forEachJdbcValue( session ), offset, + x, + y, consumer, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java index da040bb511f3..5e5e88576f67 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java @@ -298,10 +298,12 @@ public DomainResult createDomainResult(NavigablePath navigablePath, Table } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { int span = 0; @@ -311,7 +313,7 @@ public int forEachJdbcValue( continue; } final Object o = attributeMapping.getPropertyAccess().getGetter().get( value ); - span += attributeMapping.forEachJdbcValue( o, span + offset, valuesConsumer, session ); + span += attributeMapping.forEachJdbcValue( o, span + offset, x, y, valuesConsumer, session ); } return span; } @@ -356,16 +358,18 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { final Object[] values = (Object[]) value; int span = 0; for ( int i = 0; i < attributeMappings.size(); i++ ) { final AttributeMapping mapping = attributeMappings.get( i ); - span += mapping.forEachDisassembledJdbcValue( values[i], span + offset, valuesConsumer, session ); + span += mapping.forEachDisassembledJdbcValue( values[i], span + offset, x, y, valuesConsumer, session ); } return span; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java index 7d9d91ce95b3..c0e48fd2d7c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/ArrayTupleType.java @@ -94,10 +94,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { throw new UnsupportedOperationException(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressible.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressible.java index e9a7de182771..cac2317689c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressible.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/TupleMappingModelExpressible.java @@ -42,24 +42,28 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { final Object[] values = (Object[]) value; int span = 0; for ( int i = 0; i < components.length; i++ ) { - span += components[i].forEachDisassembledJdbcValue( values[i], span + offset, valuesConsumer, session ); + span += components[i].forEachDisassembledJdbcValue( values[i], span + offset, x, y, valuesConsumer, session ); } return span; } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { final Object[] values = (Object[]) value; int span = 0; @@ -67,7 +71,7 @@ public int forEachJdbcValue( span += components[i].forEachDisassembledJdbcValue( components[i].disassemble( values[i], session ), span + offset, - valuesConsumer, + x, y, valuesConsumer, session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 2ff5362444e5..db063c9b905e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -5840,25 +5840,29 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { return getIdentifierMapping() - .forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + .forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer consumer, + X x, + Y y, + JdbcValuesBiConsumer consumer, SharedSessionContractImplementor session) { final EntityIdentifierMapping identifierMapping = getIdentifierMapping(); final Object identifier = value == null ? null : identifierMapping.disassemble( identifierMapping.getIdentifier( value ), session ); - return identifierMapping.forEachDisassembledJdbcValue( identifier, offset, consumer, session ); + return identifierMapping.forEachDisassembledJdbcValue( identifier, offset, x, y, consumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityTableMapping.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityTableMapping.java index d07c8022a170..e139a1eb6a4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityTableMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityTableMapping.java @@ -229,9 +229,11 @@ public void breakDownKeyJdbcValues( SharedSessionContractImplementor session) { identifierPart.forEachJdbcValue( domainValue, - (selectionIndex, jdbcValue, jdbcMapping) -> valueConsumer.consume( + keyColumns, + valueConsumer, + (selectionIndex, keys, consumer, jdbcValue, jdbcMapping) -> consumer.consume( jdbcValue, - keyColumns.get( selectionIndex ) + keys.get( selectionIndex ) ), session ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java index 243b6fd4c5de..f805ad67d191 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java @@ -280,12 +280,14 @@ public void applySqlSelections( } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, getJdbcMapping() ); + valuesConsumer.consume( offset, x, y, value, getJdbcMapping() ); return getJdbcTypeCount(); } @@ -306,12 +308,14 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, getJdbcMapping() ); + valuesConsumer.consume( offset, x, y, value, getJdbcMapping() ); return getJdbcTypeCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java index 2f3cb0a6a0d3..a86efa703a8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java @@ -431,33 +431,37 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { final Object[] values = (Object[]) value; int span = 0; int i = 0; for ( ModelPart mapping : modelParts.values() ) { - span += mapping.forEachDisassembledJdbcValue( values[i], span + offset, valuesConsumer, session ); + span += mapping.forEachDisassembledJdbcValue( values[i], span + offset, x, y, valuesConsumer, session ); i++; } return span; } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer consumer, + X x, + Y y, + JdbcValuesBiConsumer consumer, SharedSessionContractImplementor session) { final Object[] values = (Object[]) value; int span = 0; int i = 0; for ( ModelPart attributeMapping : modelParts.values() ) { final Object o = values[i]; - span += attributeMapping.forEachJdbcValue( o, span + offset, consumer, session ); + span += attributeMapping.forEachJdbcValue( o, span + offset, x, y, consumer, session ); i++; } return span; diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java index 3fd6b0b84593..b6b4b24555ea 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java @@ -563,21 +563,25 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return delegate.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return delegate.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer consumer, + X x, + Y y, + JdbcValuesBiConsumer consumer, SharedSessionContractImplementor session) { - return delegate.forEachJdbcValue( value, offset, consumer, session ); + return delegate.forEachJdbcValue( value, offset, x, y, consumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleTableGroupProducer.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleTableGroupProducer.java index ed7e24571b2d..3cd91b1717bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleTableGroupProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleTableGroupProducer.java @@ -42,8 +42,6 @@ import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.SqlSelection; -import org.hibernate.sql.ast.tree.expression.Expression; -import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.from.LazyTableGroup; import org.hibernate.sql.ast.tree.from.PluralTableGroup; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -389,10 +387,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { throw new UnsupportedOperationException( "Not yet implemented" ); } 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 e8fcb408870d..a1e55317678f 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 @@ -272,7 +272,6 @@ import org.hibernate.query.sqm.tree.update.SqmAssignment; import org.hibernate.query.sqm.tree.update.SqmSetClause; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; -import org.hibernate.spi.EntityIdentifierNavigablePath; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstJoinType; @@ -5063,8 +5062,10 @@ else if ( basicValuedMapping instanceof EntityDiscriminatorMapping ) { final List list = new ArrayList<>( embeddableValuedModelPart.getJdbcTypeCount() ); embeddableValuedModelPart.forEachJdbcValue( literal.getLiteralValue(), - (selectionIndex, value, jdbcMapping) - -> list.add( new QueryLiteral<>( value, (BasicValuedMapping) jdbcMapping ) ), + list, + null, + (selectionIndex, expressions, noop, value, jdbcMapping) + -> expressions.add( new QueryLiteral<>( value, (BasicValuedMapping) jdbcMapping ) ), null ); return new SqlTuple( list, expressible ); @@ -5107,8 +5108,10 @@ else if ( expressible instanceof EntityValuedModelPart ) { final List list = new ArrayList<>( associationKeyPart.getJdbcTypeCount() ); associationKeyPart.forEachJdbcValue( associationKey, - (selectionIndex, value, jdbcMapping) - -> list.add( new QueryLiteral<>( value, (BasicValuedMapping) jdbcMapping ) ), + list, + null, + (selectionIndex, expressions, noop, value, jdbcMapping) + -> expressions.add( new QueryLiteral<>( value, (BasicValuedMapping) jdbcMapping ) ), null ); return new SqlTuple( list, associationKeyPart ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/EntityTypeLiteral.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/EntityTypeLiteral.java index c97b24ecc4a0..555141e2ffce 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/EntityTypeLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/EntityTypeLiteral.java @@ -80,21 +80,25 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return discriminatorType.forEachDisassembledJdbcValue( value, offset, valuesConsumer, session ); + return discriminatorType.forEachDisassembledJdbcValue( value, offset, x, y, valuesConsumer, session ); } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - return discriminatorType.forEachJdbcValue( value, offset, valuesConsumer, session ); + return discriminatorType.forEachJdbcValue( value, offset, x, y, valuesConsumer, session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java index f75471a81259..f59bb2e76af2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/JdbcLiteral.java @@ -119,22 +119,26 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, jdbcMapping ); + valuesConsumer.consume( offset, x, y, value, jdbcMapping ); return getJdbcTypeCount(); } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, jdbcMapping ); + valuesConsumer.consume( offset, x, y, value, jdbcMapping ); return getJdbcTypeCount(); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java index 6a121ef694f5..cf31c676c672 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/AbstractJdbcParameter.java @@ -155,22 +155,26 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, jdbcMapping ); + valuesConsumer.consume( offset, x, y, value, jdbcMapping ); return getJdbcTypeCount(); } @Override - public int forEachJdbcValue( + public int forEachJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, jdbcMapping ); + valuesConsumer.consume( offset, x, y, value, jdbcMapping ); return getJdbcTypeCount(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java index deabfafacc06..852f7ce53750 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcParameterBindings.java @@ -88,14 +88,16 @@ default int registerParametersForEachJdbcValue( return bindable.forEachJdbcValue( value, offset, - (selectionIndex, jdbcValue, type) -> { + jdbcParameters, + session.getFactory().getTypeConfiguration(), + (selectionIndex, params, typeConfiguration, jdbcValue, type) -> { addBinding( - jdbcParameters.get( selectionIndex ), + params.get( selectionIndex ), new JdbcParameterBindingImpl( BindingTypeHelper.INSTANCE.resolveBindType( jdbcValue, type, - session.getFactory().getTypeConfiguration() + typeConfiguration ), jdbcValue ) diff --git a/hibernate-core/src/main/java/org/hibernate/type/BasicType.java b/hibernate-core/src/main/java/org/hibernate/type/BasicType.java index 783b0b500402..68710d96ab2d 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/BasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/BasicType.java @@ -123,12 +123,14 @@ default Object disassemble(Object value, SharedSessionContractImplementor sessio } @Override - default int forEachDisassembledJdbcValue( + default int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { - valuesConsumer.consume( offset, value, getJdbcMapping() ); + valuesConsumer.consume( offset, x, y, value, getJdbcMapping() ); return getJdbcTypeCount(); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java index 8f733e129dc8..d71241172854 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java @@ -193,10 +193,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { return 0; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java index b65dfc23a083..ea01b7758861 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java @@ -220,10 +220,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { return 0; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java index a2921106b802..fba7c85cf79a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java @@ -181,10 +181,12 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public int forEachDisassembledJdbcValue( + public int forEachDisassembledJdbcValue( Object value, int offset, - JdbcValuesConsumer valuesConsumer, + X x, + Y y, + JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { return 0; } From b1b45a7b7a708957db2e04e62037d3cd7694444d Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 10 Feb 2023 18:22:09 +0100 Subject: [PATCH 0066/1497] Use an instant in tests that fits the millisecond resolution of Sybase --- .../mutability/attribute/BasicAttributeMutabilityTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/BasicAttributeMutabilityTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/BasicAttributeMutabilityTests.java index c803d58d6818..e4b118f77d6d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/BasicAttributeMutabilityTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/mutability/attribute/BasicAttributeMutabilityTests.java @@ -46,7 +46,7 @@ @DomainModel( annotatedClasses = BasicAttributeMutabilityTests.TheEntity.class ) @SessionFactory public class BasicAttributeMutabilityTests { - private static final Instant START = Instant.now(); + private static final Instant START = Instant.ofEpochMilli( 1676049527493L ); @Test public void verifyDomainModel(DomainModelScope domainModelScope, SessionFactoryScope sfSessionFactoryScope) { From 48682d41042e7ea9b7019cd5a3b50125498e8c81 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 10 Feb 2023 11:17:31 +0100 Subject: [PATCH 0067/1497] very minor jdoc changes --- .../main/java/org/hibernate/annotations/UuidGenerator.java | 6 +++--- .../registry/selector/internal/StrategySelectorImpl.java | 2 +- .../org/hibernate/boot/registry/selector/package-info.java | 4 ++-- .../hibernate/boot/registry/selector/spi/package-info.java | 5 ++++- .../src/main/java/org/hibernate/mapping/Property.java | 4 ++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/UuidGenerator.java b/hibernate-core/src/main/java/org/hibernate/annotations/UuidGenerator.java index 4d36705f25ea..258307739595 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/UuidGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/UuidGenerator.java @@ -30,11 +30,11 @@ enum Style { /** - * Defaults to {@link #RANDOM} + * Defaults to {@link #RANDOM}. */ AUTO, /** - * Uses {@link UUID#randomUUID()} to generate values + * Uses {@link UUID#randomUUID()} to generate values. */ RANDOM, /** @@ -48,7 +48,7 @@ enum Style { } /** - * Which style of generation should be used + * Specifies which {@linkplain Style style} of UUID generation should be used. */ Style style() default Style.AUTO; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorImpl.java index f86b8077b325..dab4471b77c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/internal/StrategySelectorImpl.java @@ -24,7 +24,7 @@ import org.jboss.logging.Logger; /** - * Standard implementation of the StrategySelector contract. + * Standard implementation of the {@link StrategySelector} contract. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/package-info.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/package-info.java index 01cd733d8208..382ce0b95dd1 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/package-info.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/package-info.java @@ -6,7 +6,7 @@ */ /** - * Defines a feature-set around named registration of implementations of various contracts and the ability - * to select those implementations. + * Defines a feature set around named registration of implementations of various contracts + * and the ability to select those implementations. */ package org.hibernate.boot.registry.selector; diff --git a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/spi/package-info.java b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/spi/package-info.java index 587349b0095c..6de2377756a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/spi/package-info.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/registry/selector/spi/package-info.java @@ -6,6 +6,9 @@ */ /** - * Defines actual contract used for strategy selection : {@link org.hibernate.boot.registry.selector.spi.StrategySelector}. + * Defines actual contract used for + * {@linkplain org.hibernate.boot.registry.selector.spi.StrategySelector strategy selection}. + * + * @link org.hibernate.boot.registry.selector.spi.StrategySelector */ package org.hibernate.boot.registry.selector.spi; diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index 0b642a33003b..3e0e9bcfc9a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -35,8 +35,8 @@ import org.hibernate.type.Type; /** - * A mapping model object representing a property or field of an {@link PersistentClass entity} - * or {@link Component embeddable class}. + * A mapping model object representing a property or field of an {@linkplain PersistentClass entity} + * or {@linkplain Component embeddable class}. * * @author Gavin King */ From b3b293578ea399f541ad6bdc7d3a172aea1d90d7 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Fri, 10 Feb 2023 13:09:50 +0100 Subject: [PATCH 0068/1497] HHH-16133 allow before-execution generators for embeddable properties and by side-effect allow @TenantId for embeddable properties --- .../internal/TenantIdGeneration.java | 4 +- .../entity/CompositeGeneratorBuilder.java | 226 +++++++++++------- .../tuple/entity/EntityMetamodel.java | 5 +- .../hibernate/orm/test/tenantid/Record.java | 12 + .../hibernate/orm/test/tenantid/State.java | 14 ++ .../orm/test/tenantid/TenantIdTest.java | 28 ++- 6 files changed, 193 insertions(+), 96 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/Record.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/State.java diff --git a/hibernate-core/src/main/java/org/hibernate/generator/internal/TenantIdGeneration.java b/hibernate-core/src/main/java/org/hibernate/generator/internal/TenantIdGeneration.java index 3112e11a32ee..9a7ce014f5f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/generator/internal/TenantIdGeneration.java +++ b/hibernate-core/src/main/java/org/hibernate/generator/internal/TenantIdGeneration.java @@ -37,7 +37,9 @@ public class TenantIdGeneration implements BeforeExecutionGenerator { private final Class propertyType; public TenantIdGeneration(TenantId annotation, Member member, GeneratorCreationContext context) { - entityName = context.getPersistentClass().getEntityName(); + entityName = context.getPersistentClass() == null + ? member.getDeclaringClass().getName() //it's an attribute of an embeddable + : context.getPersistentClass().getEntityName(); propertyName = context.getProperty().getName(); propertyType = getPropertyType( member ); } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/CompositeGeneratorBuilder.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/CompositeGeneratorBuilder.java index e5e9fd3a88f3..aa507b68e86d 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/CompositeGeneratorBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/CompositeGeneratorBuilder.java @@ -7,12 +7,16 @@ package org.hibernate.tuple.entity; import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.generator.BeforeExecutionGenerator; import org.hibernate.generator.EventType; import org.hibernate.generator.Generator; import org.hibernate.generator.OnExecutionGenerator; import org.hibernate.mapping.Component; import org.hibernate.mapping.Property; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.persister.entity.EntityPersister; import java.util.ArrayList; import java.util.EnumSet; @@ -25,137 +29,175 @@ * Handles value generation for composite properties. */ class CompositeGeneratorBuilder { + private final String entityName; private final Property mappingProperty; private final Dialect dialect; private boolean hadBeforeExecutionGeneration; private boolean hadOnExecutionGeneration; - private List onExecutionGenerators; + private final List generators = new ArrayList<>(); - public CompositeGeneratorBuilder(Property mappingProperty, Dialect dialect) { + public CompositeGeneratorBuilder(String entityName, Property mappingProperty, Dialect dialect) { + this.entityName = entityName; this.mappingProperty = mappingProperty; this.dialect = dialect; } public void add(Generator generator) { - if ( generator != null ) { + generators.add( generator ); + + if ( generator != null && generator.generatesSometimes() ) { if ( generator.generatedOnExecution() ) { - if ( generator instanceof OnExecutionGenerator ) { - add( (OnExecutionGenerator) generator ); - } + hadOnExecutionGeneration = true; } else { - if ( generator instanceof BeforeExecutionGenerator ) { - add( (BeforeExecutionGenerator) generator ); - } + hadBeforeExecutionGeneration = true; } } } - private void add(BeforeExecutionGenerator beforeExecutionGenerator) { - if ( beforeExecutionGenerator.generatesSometimes() ) { - hadBeforeExecutionGeneration = true; - } - } - - private void add(OnExecutionGenerator onExecutionGenerator) { - if ( onExecutionGenerators == null ) { - onExecutionGenerators = new ArrayList<>(); - } - onExecutionGenerators.add( onExecutionGenerator ); - - if ( onExecutionGenerator.generatesSometimes() ) { - hadOnExecutionGeneration = true; - } - } - public Generator build() { - if ( hadBeforeExecutionGeneration && hadOnExecutionGeneration) { + if ( hadBeforeExecutionGeneration && hadOnExecutionGeneration ) { throw new CompositeValueGenerationException( - "Composite attribute [" + mappingProperty.getName() + "] contained both in-memory" - + " and in-database value generation" + "Composite attribute contained both on-execution and before-execution generators: " + + mappingProperty.getName() ); } else if ( hadBeforeExecutionGeneration ) { - throw new UnsupportedOperationException("Composite in-memory value generation not supported"); - + return createCompositeBeforeExecutionGenerator(); } else if ( hadOnExecutionGeneration ) { - final Component composite = (Component) mappingProperty.getValue(); + return createCompositeOnExecutionGenerator(); + } + else { + return new Generator() { + @Override + public EnumSet getEventTypes() { + return NONE; + } + @Override + public boolean generatedOnExecution() { + return false; + } + }; + } + } - // we need the numbers to match up so that we can properly handle 'referenced sql column values' - if ( onExecutionGenerators.size() != composite.getPropertySpan() ) { + private OnExecutionGenerator createCompositeOnExecutionGenerator() { + final Component composite = (Component) mappingProperty.getValue(); + + // the base-line values for the aggregated OnExecutionGenerator we will build here. + final EnumSet eventTypes = EnumSet.noneOf(EventType.class); + boolean referenceColumns = false; + final String[] columnValues = new String[composite.getColumnSpan()]; + + // start building the aggregate values + int columnIndex = 0; + final List properties = composite.getProperties(); + for ( int i = 0; i < properties.size(); i++ ) { + final Property property = properties.get(i); + final OnExecutionGenerator generator = (OnExecutionGenerator) generators.get(i); + if ( generator == null ) { throw new CompositeValueGenerationException( - "Internal error : mismatch between number of collected in-db generation strategies" + - " and number of attributes for composite attribute : " + mappingProperty.getName() + "Property of on-execution generated embeddable is not generated: " + + mappingProperty.getName() + '.' + property.getName() ); } - - // the base-line values for the aggregated OnExecutionGenerator we will build here. - final EnumSet eventTypes = EnumSet.noneOf(EventType.class); - boolean referenceColumns = false; - final String[] columnValues = new String[composite.getColumnSpan()]; - - // start building the aggregate values - int propertyIndex = -1; - int columnIndex = 0; - for ( Property property : composite.getProperties() ) { - propertyIndex++; - final OnExecutionGenerator generator = onExecutionGenerators.get( propertyIndex ); - eventTypes.addAll( generator.getEventTypes() ); - if ( generator.referenceColumnsInSql( dialect ) ) { - // override base-line value - referenceColumns = true; - final String[] referencedColumnValues = generator.getReferencedColumnValues( dialect ); - if ( referencedColumnValues != null ) { - final int span = property.getColumnSpan(); - if ( referencedColumnValues.length != span ) { - throw new CompositeValueGenerationException( - "Mismatch between number of collected generated column values and number of columns for composite attribute: " - + mappingProperty.getName() + '.' + property.getName() - ); - } - arraycopy( referencedColumnValues, 0, columnValues, columnIndex, span ); + eventTypes.addAll( generator.getEventTypes() ); + if ( generator.referenceColumnsInSql( dialect ) ) { + // override base-line value + referenceColumns = true; + final String[] referencedColumnValues = generator.getReferencedColumnValues( dialect ); + if ( referencedColumnValues != null ) { + final int span = property.getColumnSpan(); + if ( referencedColumnValues.length != span ) { + throw new CompositeValueGenerationException( + "Mismatch between number of collected generated column values and number of columns for composite attribute: " + + mappingProperty.getName() + '.' + property.getName() + ); } + arraycopy( referencedColumnValues, 0, columnValues, columnIndex, span ); + columnIndex += span; } } - final boolean referenceColumnsInSql = referenceColumns; + } + final boolean referenceColumnsInSql = referenceColumns; - // then use the aggregated values to build an OnExecutionGenerator - return new OnExecutionGenerator() { - @Override - public EnumSet getEventTypes() { - return eventTypes; - } + // then use the aggregated values to build an OnExecutionGenerator + return new OnExecutionGenerator() { + @Override + public EnumSet getEventTypes() { + return eventTypes; + } - @Override - public boolean referenceColumnsInSql(Dialect dialect) { - return referenceColumnsInSql; - } + @Override + public boolean referenceColumnsInSql(Dialect dialect) { + return referenceColumnsInSql; + } - @Override - public String[] getReferencedColumnValues(Dialect dialect) { - return columnValues; - } + @Override + public String[] getReferencedColumnValues(Dialect dialect) { + return columnValues; + } - @Override - public boolean writePropertyValue() { - return false; - } - }; + @Override + public boolean writePropertyValue() { + return false; + } + }; + } + + private BeforeExecutionGenerator createCompositeBeforeExecutionGenerator() { + final Component composite = (Component) mappingProperty.getValue(); + final EnumSet eventTypes = EnumSet.noneOf(EventType.class); + final List properties = composite.getProperties(); + for ( int i = 0; i < properties.size(); i++ ) { + final Generator generator = generators.get(i); + if ( generator != null ) { + eventTypes.addAll( generator.getEventTypes() ); + } } - else { - return new Generator() { - @Override - public EnumSet getEventTypes() { - return NONE; + return new BeforeExecutionGenerator() { + @Override + public Object generate(SharedSessionContractImplementor session, Object owner, Object currentValue, EventType eventType) { + final EntityPersister persister = session.getEntityPersister( entityName, owner ); + final int index = persister.getPropertyIndex( mappingProperty.getName() ); + final EmbeddableMappingType descriptor = + persister.getAttributeMapping(index).asEmbeddedAttributeMapping() + .getEmbeddableTypeDescriptor(); + final int size = properties.size(); + if ( currentValue == null ) { + final Object[] generatedValues = new Object[size]; + for ( int i = 0; i < size; i++ ) { + final Generator generator = generators.get(i); + if ( generator != null ) { + generatedValues[i] = ((BeforeExecutionGenerator) generator) + .generate( session, owner, null, eventType ); + } + } + return descriptor.getRepresentationStrategy().getInstantiator() + .instantiate( () -> generatedValues, session.getFactory() ); } - @Override - public boolean generatedOnExecution() { - return false; + else { + for ( int i = 0; i < size; i++ ) { + final Generator generator = generators.get(i); + if ( generator != null ) { + final AttributeMapping attributeMapping = descriptor.getAttributeMapping(i); + final Object value = attributeMapping.getPropertyAccess().getGetter().get( currentValue ); + final Object generatedValue = ((BeforeExecutionGenerator) generator) + .generate( session, owner, value, eventType ); + attributeMapping.getPropertyAccess().getSetter().set( currentValue, generatedValue ); + } + } + return currentValue; } - }; - } + } + + @Override + public EnumSet getEventTypes() { + return eventTypes; + } + }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 673c316b6a40..ee0a998bae28 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -309,7 +309,7 @@ public EntityMetamodel( // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // generated value strategies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - final Generator generator = buildGenerator( property, creationContext ); + final Generator generator = buildGenerator( name, property, creationContext ); if ( generator != null ) { if ( i == tempVersionProperty && !generator.generatedOnExecution() ) { // when we have an in-memory generator for the version, we @@ -469,6 +469,7 @@ private static boolean generatedWithNoParameter(Generator generator) { } private static Generator buildGenerator( + final String entityName, final Property mappingProperty, final RuntimeModelCreationContext context) { final GeneratorCreator generatorCreator = mappingProperty.getValueGeneratorCreator(); @@ -480,7 +481,7 @@ private static Generator buildGenerator( } if ( mappingProperty.getValue() instanceof Component ) { final Dialect dialect = context.getDialect(); - final CompositeGeneratorBuilder builder = new CompositeGeneratorBuilder( mappingProperty, dialect ); + final CompositeGeneratorBuilder builder = new CompositeGeneratorBuilder( entityName, mappingProperty, dialect ); final Component component = (Component) mappingProperty.getValue(); for ( Property property : component.getProperties() ) { builder.add( property.createGenerator( context ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/Record.java b/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/Record.java new file mode 100644 index 000000000000..479ecbebcf7f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/Record.java @@ -0,0 +1,12 @@ +package org.hibernate.orm.test.tenantid; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Record { + @Id @GeneratedValue + public Long id; + public State state = new State(); +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/State.java b/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/State.java new file mode 100644 index 000000000000..589dff6ac193 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/State.java @@ -0,0 +1,14 @@ +package org.hibernate.orm.test.tenantid; + +import jakarta.persistence.Embeddable; +import org.hibernate.annotations.TenantId; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.Instant; + +@Embeddable +public class State { + public boolean deleted; + public @TenantId String tenantId; + public @UpdateTimestamp Instant updated; +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/TenantIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/TenantIdTest.java index e470a6d18c0d..a4e2ed40b90b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/TenantIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/TenantIdTest.java @@ -10,6 +10,7 @@ import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; +import org.hibernate.dialect.SybaseASEDialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.ServiceRegistry; @@ -18,6 +19,7 @@ import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; import org.hibernate.binder.internal.TenantIdBinder; +import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -26,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; @SessionFactory -@DomainModel(annotatedClasses = { Account.class, Client.class }) +@DomainModel(annotatedClasses = { Account.class, Client.class, Record.class }) @ServiceRegistry( settings = { @Setting(name = HBM2DDL_DATABASE_ACTION, value = "create-drop") @@ -124,4 +126,28 @@ public void testErrorOnUpdate(SessionFactoryScope scope) { assertEquals( "mine", acc.client.tenantId ); } ); } + + @Test + @SkipForDialect(dialectClass = SybaseASEDialect.class, + reason = "low timestamp precision on Sybase") + public void testEmbeddedTenantId(SessionFactoryScope scope) { + currentTenant = "mine"; + Record record = new Record(); + scope.inTransaction( s -> s.persist( record ) ); + assertEquals( "mine", record.state.tenantId ); + assertNotNull( record.state.updated ); + scope.inTransaction( s -> { + Record r = s.find( Record.class, record.id ); + assertEquals( "mine", r.state.tenantId ); + assertEquals( record.state.updated, r.state.updated ); + assertEquals( false, r.state.deleted ); + r.state.deleted = true; + } ); + scope.inTransaction( s -> { + Record r = s.find( Record.class, record.id ); + assertEquals( "mine", r.state.tenantId ); + assertNotEquals( record.state.updated, r.state.updated ); + assertEquals( true, r.state.deleted ); + } ); + } } From 02a4f91d461cfede5626d1e5401c25d47b4a5d09 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 13 Feb 2023 10:06:50 +0100 Subject: [PATCH 0069/1497] Fix TenantIdTest when VM has nanosecond precision --- .../type/descriptor/java/ClockHelper.java | 40 +++++++++---------- .../orm/test/tenantid/TenantIdTest.java | 7 ++++ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClockHelper.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClockHelper.java index 70813da25929..d199e6156093 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClockHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/java/ClockHelper.java @@ -16,15 +16,16 @@ */ public class ClockHelper { - private static final Duration TICK_8 = Duration.ofNanos( 10L ); - private static final Duration TICK_7 = Duration.ofNanos( 100L ); - private static final Duration TICK_6 = Duration.ofNanos( 1000L ); - private static final Duration TICK_5 = Duration.ofNanos( 10000L ); - private static final Duration TICK_4 = Duration.ofNanos( 100000L ); - private static final Duration TICK_3 = Duration.ofNanos( 1000000L ); - private static final Duration TICK_2 = Duration.ofNanos( 10000000L ); - private static final Duration TICK_1 = Duration.ofNanos( 100000000L ); - private static final Duration TICK_0 = Duration.ofNanos( 1000000000L ); + private static final Clock TICK_9 = Clock.systemDefaultZone(); + private static final Clock TICK_8 = Clock.tick( TICK_9, Duration.ofNanos( 10L ) ); + private static final Clock TICK_7 = Clock.tick( TICK_9, Duration.ofNanos( 100L ) ); + private static final Clock TICK_6 = Clock.tick( TICK_9, Duration.ofNanos( 1000L ) ); + private static final Clock TICK_5 = Clock.tick( TICK_9, Duration.ofNanos( 10000L ) ); + private static final Clock TICK_4 = Clock.tick( TICK_9, Duration.ofNanos( 100000L ) ); + private static final Clock TICK_3 = Clock.tick( TICK_9, Duration.ofNanos( 1000000L ) ); + private static final Clock TICK_2 = Clock.tick( TICK_9, Duration.ofNanos( 10000000L ) ); + private static final Clock TICK_1 = Clock.tick( TICK_9, Duration.ofNanos( 100000000L ) ); + private static final Clock TICK_0 = Clock.tick( TICK_9, Duration.ofNanos( 1000000000L ) ); public static Clock forPrecision(Integer precision, SharedSessionContractImplementor session) { final int resolvedPrecision; @@ -34,28 +35,27 @@ public static Clock forPrecision(Integer precision, SharedSessionContractImpleme else { resolvedPrecision = precision; } - final Clock clock = Clock.systemDefaultZone(); switch ( resolvedPrecision ) { case 0: - return Clock.tick( clock, TICK_0 ); + return TICK_0; case 1: - return Clock.tick( clock, TICK_1 ); + return TICK_1; case 2: - return Clock.tick( clock, TICK_2 ); + return TICK_2; case 3: - return Clock.tick( clock, TICK_3 ); + return TICK_3; case 4: - return Clock.tick( clock, TICK_4 ); + return TICK_4; case 5: - return Clock.tick( clock, TICK_5 ); + return TICK_5; case 6: - return Clock.tick( clock, TICK_6 ); + return TICK_6; case 7: - return Clock.tick( clock, TICK_7 ); + return TICK_7; case 8: - return Clock.tick( clock, TICK_8 ); + return TICK_8; case 9: - return clock; + return TICK_9; } throw new IllegalArgumentException( "Illegal precision: " + resolvedPrecision ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/TenantIdTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/TenantIdTest.java index a4e2ed40b90b..832f4537f370 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/TenantIdTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/tenantid/TenantIdTest.java @@ -19,6 +19,8 @@ import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; import org.hibernate.binder.internal.TenantIdBinder; +import org.hibernate.type.descriptor.DateTimeUtils; + import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -136,6 +138,11 @@ public void testEmbeddedTenantId(SessionFactoryScope scope) { scope.inTransaction( s -> s.persist( record ) ); assertEquals( "mine", record.state.tenantId ); assertNotNull( record.state.updated ); + // Round the temporal to avoid issues when the VM produces nanosecond precision timestamps + record.state.updated = DateTimeUtils.roundToDefaultPrecision( + record.state.updated, + scope.getSessionFactory().getJdbcServices().getDialect() + ); scope.inTransaction( s -> { Record r = s.find( Record.class, record.id ); assertEquals( "mine", r.state.tenantId ); From 6c7603f83aa3e17ad57c97e0e956b091939c0b58 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Fri, 10 Feb 2023 19:30:54 +0100 Subject: [PATCH 0070/1497] HHH-16131 - Correction to workaround for date calculation errors on Oracle (first workaround failed on Oracle 11) Signed-off-by: Jan Schatteman --- .../src/main/java/org/hibernate/dialect/OracleDialect.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 ee0e97e1a7a6..c36fd0e51832 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -151,8 +151,7 @@ public class OracleDialect extends Dialect { public static final String PREFER_LONG_RAW = "hibernate.dialect.oracle.prefer_long_raw"; private static final String yqmSelect = - "( SELECT b_.bd + ( LEAST( EXTRACT( DAY FROM b_.od ), EXTRACT( DAY FROM LAST_DAY( b_.bd ) ) ) - 1 )\n" + - "FROM (SELECT a_.od, TRUNC(a_.od, 'MONTH') + NUMTOYMINTERVAL(%1$s, 'MONTH') bd FROM ( SELECT %2$s od FROM dual ) a_) b_ ) "; + "( TRUNC(%2$s, 'MONTH') + NUMTOYMINTERVAL(%1$s, 'MONTH') + ( LEAST( EXTRACT( DAY FROM %2$s ), EXTRACT( DAY FROM LAST_DAY( TRUNC(%2$s, 'MONTH') + NUMTOYMINTERVAL(%1$s, 'MONTH') ) ) ) - 1 ) )"; private static final String ADD_YEAR_EXPRESSION = String.format( yqmSelect, "?2*12", "?3" ); private static final String ADD_QUARTER_EXPRESSION = String.format( yqmSelect, "?2*3", "?3" ); From bb14d5b288ec230bc78b0e0a2a735d5bd54adf0f Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sat, 11 Feb 2023 00:11:01 +0100 Subject: [PATCH 0071/1497] HHH-16174 support for extract(epoch from ...) in HQL --- .../userguide/chapters/query/hql/QueryLanguage.adoc | 2 +- .../main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 | 1 + .../antlr/org/hibernate/grammars/hql/HqlParser.g4 | 2 ++ .../main/java/org/hibernate/dialect/DerbyDialect.java | 2 ++ .../main/java/org/hibernate/dialect/HSQLDialect.java | 10 ++++++++++ .../main/java/org/hibernate/dialect/MySQLDialect.java | 2 ++ .../java/org/hibernate/dialect/OracleDialect.java | 2 ++ .../java/org/hibernate/dialect/SQLServerDialect.java | 2 ++ .../java/org/hibernate/dialect/SybaseDialect.java | 9 +++++++-- .../query/hql/internal/SemanticQueryBuilder.java | 6 ++++++ .../java/org/hibernate/query/sqm/TemporalUnit.java | 3 +-- .../hibernate/orm/test/query/hql/FunctionTests.java | 11 +++++++++++ 12 files changed, 47 insertions(+), 5 deletions(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc index 13c81154ae1f..d45d5be7adff 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/hql/QueryLanguage.adoc @@ -814,7 +814,7 @@ There are two very important function for working with dates and times. The special function `extract()` obtains a single field of a date, time, or datetime. -Field types include: `day`, `month`, `year`, `second`, `minute`, `hour`, `day of week`, `day of month`, `week of year`, `date`, `time` and more. +Field types include: `day`, `month`, `year`, `second`, `minute`, `hour`, `day of week`, `day of month`, `week of year`, `date`, `time`, `epoch` and more. For a full list of field types, see the Javadoc for https://docs.jboss.org/hibernate/orm/{majorMinorVersion}/javadocs/org/hibernate/query/TemporalUnit.html[`TemporalUnit`]. ==== diff --git a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 index 96870f50d885..40d2322203d7 100644 --- a/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 +++ b/hibernate-core/src/main/antlr/org/hibernate/grammars/hql/HqlLexer.g4 @@ -181,6 +181,7 @@ ELSE : [eE] [lL] [sS] [eE]; EMPTY : [eE] [mM] [pP] [tT] [yY]; END : [eE] [nN] [dD]; ENTRY : [eE] [nN] [tT] [rR] [yY]; +EPOCH : [eE] [pP] [oO] [cC] [hH]; ERROR : [eE] [rR] [rR] [oO] [rR]; ESCAPE : [eE] [sS] [cC] [aA] [pP] [eE]; EVERY : [eE] [vV] [eE] [rR] [yY]; 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 260cc129fef8..8628acef1753 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 @@ -1473,6 +1473,7 @@ datetimeField | MINUTE | SECOND | NANOSECOND + | EPOCH ; dayField @@ -1577,6 +1578,7 @@ rollup | EMPTY | END | ENTRY + | EPOCH | ERROR | ESCAPE | EVERY 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 bf534203bc2c..314f9020b9fe 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbyDialect.java @@ -368,6 +368,8 @@ public String extractPattern(TemporalUnit unit) { return "(({fn timestampdiff(sql_tsi_day,date(char(year(?2),4)||'-01-01'),{fn timestampadd(sql_tsi_day,{fn timestampdiff(sql_tsi_day,{d '1753-01-01'},?2)}/7*7,{d '1753-01-04'})})}+7)/7)"; case QUARTER: return "((month(?2)+2)/3)"; + case EPOCH: + return "{fn timestampdiff(sql_tsi_second,{ts '1970-01-01 00:00:00'},?2)}"; default: return "?1(?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 bf1a7c8e915d..9c4fb5e866ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -319,6 +319,16 @@ public String timestampdiffPattern(TemporalUnit unit, TemporalType fromTemporalT return pattern.toString(); } + @Override + public String extractPattern(TemporalUnit unit) { + if ( unit == TemporalUnit.EPOCH ) { + return "unix_timestamp(?2)"; + } + else { + return super.extractPattern(unit); + } + } + @Override public boolean supportsDistinctFromPredicate() { return true; 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 cbf2fb928961..dd7c94c38487 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -745,6 +745,8 @@ public String extractPattern(TemporalUnit unit) { case DAY_OF_YEAR: return "dayofyear(?2)"; //TODO: case WEEK_YEAR: yearweek(?2, 3)/100 + case EPOCH: + return "unix_timestamp(?2)"; default: return "?1(?2)"; } 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 c36fd0e51832..420731729020 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -480,6 +480,8 @@ public String extractPattern(TemporalUnit unit) { return "to_number(to_char(?2,'MI'))"; case SECOND: return "to_number(to_char(?2,'SS'))"; + case EPOCH: + return "trunc((cast(?2 at time zone 'UTC' as date) - date '1970-1-1')*86400)"; default: return super.extractPattern(unit); } 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 43713a0be435..25641c3eccb3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SQLServerDialect.java @@ -753,6 +753,8 @@ public String extractPattern(TemporalUnit unit) { case SECOND: //this should evaluate to a floating point type return "(datepart(second,?2)+datepart(nanosecond,?2)/1e9)"; + case EPOCH: + return "datediff_big(second, '1970-01-01', ?2)"; default: return "datepart(?1,?2)"; } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index be14fe5313a3..36ec09ed0e5d 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -300,8 +300,13 @@ public String translateExtractField(TemporalUnit unit) { @Override public String extractPattern(TemporalUnit unit) { - //TODO!! - return "datepart(?1,?2)"; + if ( unit == TemporalUnit.EPOCH ) { + return "datediff(second, '1970-01-01 00:00:00', ?2)"; + } + else { + //TODO!! + return "datepart(?1,?2)"; + } } @Override 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 ae8434bc6abe..fccd78a97b6a 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 @@ -4199,6 +4199,12 @@ public SqmExtractUnit visitDatetimeField(HqlParser.DatetimeFieldContext ctx) resolveExpressibleTypeBasic( Integer.class ), nodeBuilder ); + case HqlParser.EPOCH: + return new SqmExtractUnit<>( + TemporalUnit.EPOCH, + resolveExpressibleTypeBasic( Long.class ), + nodeBuilder + ); } throw new ParsingException( "Unsupported datetime field [" + ctx.getText() + "]" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/TemporalUnit.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/TemporalUnit.java index d72f5fd001e0..ac631aadc0dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/TemporalUnit.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/TemporalUnit.java @@ -154,8 +154,7 @@ public enum TemporalUnit { TIME, /** * An internal value representing the Unix epoch, the elapsed - * seconds since January 1, 1970. Currently not supported in - * HQL. + * seconds since January 1, 1970. */ EPOCH, /** 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 173aa1e7aa88..79693752bf00 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 @@ -1518,6 +1518,17 @@ public void testExtractFunction(SessionFactoryScope scope) { ); } + @Test + public void testExtractFunctionEpoch(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery("select extract(epoch from local datetime)", Long.class).getSingleResult(); + session.createQuery("select extract(epoch from offset datetime)", Long.class).getSingleResult(); + assertThat( session.createQuery("select extract(epoch from datetime 1974-03-23 12:35)", Long.class).getSingleResult(), is(133274100L) ); + } + ); + } + @Test public void testExtractFunctionWeek(SessionFactoryScope scope) { scope.inTransaction( From 108967d26d90ae39d7f0757a3b89b6a2275320f2 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Fri, 10 Feb 2023 23:07:14 +0100 Subject: [PATCH 0072/1497] HHH-16170 Add test for issue --- .../orm/test/type/EnumUpdateTest.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/type/EnumUpdateTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/type/EnumUpdateTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/type/EnumUpdateTest.java new file mode 100644 index 000000000000..ec696ee447c3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/type/EnumUpdateTest.java @@ -0,0 +1,80 @@ +/* + * 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.orm.test.type; + +import java.util.List; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = EnumUpdateTest.Person.class) +public class EnumUpdateTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.persist( new Person( HairColor.BLACK ) ); + session.persist( new Person( HairColor.BROWN ) ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from Person" ).executeUpdate() ); + } + + @Test + public void testUpdate(SessionFactoryScope scope) { + scope.inTransaction( + session -> session.createMutationQuery( "update Person set hairColor = BROWN" ).executeUpdate() + ); + scope.inTransaction( session -> { + List resultList = session.createQuery( "from Person", Person.class ).getResultList(); + assertEquals( 2, resultList.size() ); + assertEquals( HairColor.BROWN, resultList.get( 0 ).getHairColor() ); + assertEquals( HairColor.BROWN, resultList.get( 1 ).getHairColor() ); + } ); + } + + public enum HairColor { + BLACK, BLONDE, BROWN; + } + + @Entity(name = "Person") + public static class Person { + @Id + @GeneratedValue + private Long id; + + private HairColor hairColor; + + public Person() { + } + + public Person(HairColor hairColor) { + this.hairColor = hairColor; + } + + public HairColor getHairColor() { + return hairColor; + } + } +} From 7e9902c09e7ba9bfe580d3218ec4bc027416f289 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Fri, 10 Feb 2023 23:10:13 +0100 Subject: [PATCH 0073/1497] HHH-16170 Check for enums in update statement --- .../hql/internal/SemanticQueryBuilder.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) 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 fccd78a97b6a..6617bfc85861 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 @@ -573,10 +573,25 @@ public SqmUpdateStatement visitUpdateStatement(HqlParser.UpdateStatementConte if ( subCtx instanceof HqlParser.AssignmentContext ) { final HqlParser.AssignmentContext assignmentContext = (HqlParser.AssignmentContext) subCtx; //noinspection unchecked - updateStatement.applyAssignment( - (SqmPath) consumeDomainPath( (HqlParser.SimplePathContext) assignmentContext.getChild( 0 ) ), - (SqmExpression) assignmentContext.getChild( 2 ).accept( this ) - ); + final SqmPath targetPath = (SqmPath) consumeDomainPath( (HqlParser.SimplePathContext) assignmentContext.getChild( 0 ) ); + final Class targetPathJavaType = targetPath.getJavaType(); + final boolean isEnum = targetPathJavaType != null && targetPathJavaType.isEnum(); + final ParseTree rightSide = assignmentContext.getChild( 2 ); + final HqlParser.ExpressionContext expressionContext; + final Map, Enum> possibleEnumValues; + final SqmExpression value; + if ( isEnum && rightSide.getChild( 0 ) instanceof HqlParser.ExpressionContext + && ( possibleEnumValues = getPossibleEnumValues( expressionContext = (HqlParser.ExpressionContext) rightSide.getChild( 0 ) ) ) != null ) { + value = resolveEnumShorthandLiteral( + expressionContext, + possibleEnumValues, + targetPathJavaType + ); + } + else { + value = (SqmExpression) rightSide.accept( this ); + } + updateStatement.applyAssignment( targetPath, value ); } } From 60630fc48dcf662311ac23f5ee3f25630c75c6e8 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 10 Feb 2023 18:12:14 +0100 Subject: [PATCH 0074/1497] Get rid of most capturing lambdas in write path --- .../ast/internal/CompoundNaturalIdLoader.java | 2 +- .../ast/internal/SimpleNaturalIdLoader.java | 2 +- .../hibernate/metamodel/mapping/Bindable.java | 8 +- .../metamodel/mapping/ModelPart.java | 89 ++++++++++++-- .../AbstractDiscriminatorMapping.java | 11 +- .../internal/AnyDiscriminatorPart.java | 11 +- .../mapping/internal/AnyKeyPart.java | 11 +- .../internal/BasicAttributeMapping.java | 11 +- .../BasicEntityIdentifierMappingImpl.java | 11 +- .../internal/BasicValuedCollectionPart.java | 21 +++- .../CollectionIdentifierDescriptorImpl.java | 13 +- .../internal/CompoundNaturalIdMapping.java | 20 ++- ...criminatedAssociationAttributeMapping.java | 20 ++- .../DiscriminatedAssociationMapping.java | 62 ++++++---- .../internal/DiscriminatedCollectionPart.java | 20 ++- .../internal/EmbeddableMappingTypeImpl.java | 62 +++++++--- .../internal/EmbeddedAttributeMapping.java | 20 ++- .../internal/EmbeddedCollectionPart.java | 10 +- .../EmbeddedForeignKeyDescriptor.java | 34 ++++-- .../EmbeddedIdentifierMappingImpl.java | 10 +- .../internal/EntityRowIdMappingImpl.java | 11 +- .../internal/EntityVersionMappingImpl.java | 11 +- .../mapping/internal/IdClassEmbeddable.java | 17 ++- ...InverseNonAggregatedIdentifierMapping.java | 9 +- .../internal/ManyToManyCollectionPart.java | 22 +++- .../NonAggregatedIdentifierMappingImpl.java | 9 +- .../internal/OneToManyCollectionPart.java | 12 +- .../internal/PluralAttributeMappingImpl.java | 8 +- .../internal/SimpleForeignKeyDescriptor.java | 13 +- .../internal/SimpleNaturalIdMapping.java | 10 +- .../internal/ToOneAttributeMapping.java | 11 +- .../mapping/internal/VirtualIdEmbeddable.java | 32 +++-- .../collection/BasicCollectionPersister.java | 115 ++++++++++++++---- .../collection/OneToManyPersister.java | 89 +++++++++----- .../DeleteRowsCoordinatorStandard.java | 11 +- .../InsertRowsCoordinatorStandard.java | 3 +- .../mutation/RemoveCoordinatorStandard.java | 12 +- .../mutation/RowMutationOperations.java | 30 ++--- .../UpdateRowsCoordinatorOneToMany.java | 12 +- .../UpdateRowsCoordinatorStandard.java | 12 +- .../persister/entity/EntityPersister.java | 21 +++- .../mutation/AbstractMutationCoordinator.java | 46 ++++--- .../entity/mutation/DeleteCoordinator.java | 9 +- .../entity/mutation/InsertCoordinator.java | 7 +- .../mutation/UpdateCoordinatorStandard.java | 16 ++- .../AnonymousTupleBasicValuedModelPart.java | 11 +- ...onymousTupleEmbeddableValuedModelPart.java | 12 +- .../AnonymousTupleEntityValuedModelPart.java | 9 +- .../AnonymousTupleTableGroupProducer.java | 7 +- .../sql/model/ast/ColumnValueBindingList.java | 2 +- .../internal/ValidityAuditStrategy.java | 66 +++++----- 51 files changed, 774 insertions(+), 329 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CompoundNaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CompoundNaturalIdLoader.java index 56bad8aef693..442b21f4f4f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CompoundNaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CompoundNaturalIdLoader.java @@ -71,7 +71,7 @@ protected void applyNaturalIdRestriction( naturalIdMapping().breakDownJdbcValues( bindValue, - (jdbcValue, jdbcValueMapping) -> { + (valueIndex, jdbcValue, jdbcValueMapping) -> { final Expression columnReference = resolveColumnReference( rootTableGroup, jdbcValueMapping, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SimpleNaturalIdLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SimpleNaturalIdLoader.java index d3ea46780c95..9b77932d4e16 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SimpleNaturalIdLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SimpleNaturalIdLoader.java @@ -63,7 +63,7 @@ protected void applyNaturalIdRestriction( else { naturalIdMapping().getAttribute().breakDownJdbcValues( bindValue, - (jdbcValue, jdbcValueMapping) -> { + (valueIndex, jdbcValue, jdbcValueMapping) -> { final Expression columnReference = resolveColumnReference( rootTableGroup, jdbcValueMapping, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Bindable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Bindable.java index 82f04f6a8f19..a283e67d5233 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Bindable.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/Bindable.java @@ -218,14 +218,14 @@ default int forEachJdbcValue( @FunctionalInterface interface JdbcValuesConsumer extends JdbcValuesBiConsumer { @Override - default void consume(int selectionIndex, Object o, Object o2, Object jdbcValue, JdbcMapping jdbcMapping) { - consume( selectionIndex, jdbcValue, jdbcMapping ); + default void consume(int valueIndex, Object o, Object o2, Object jdbcValue, JdbcMapping jdbcMapping) { + consume( valueIndex, jdbcValue, jdbcMapping ); } /** * Consume a JDBC-level jdbcValue. The JDBC jdbcMapping descriptor is also passed in */ - void consume(int selectionIndex, Object jdbcValue, JdbcMapping jdbcMapping); + void consume(int valueIndex, Object jdbcValue, JdbcMapping jdbcMapping); } /** @@ -236,6 +236,6 @@ interface JdbcValuesBiConsumer { /** * Consume a JDBC-level jdbcValue. The JDBC jdbcMapping descriptor is also passed in */ - void consume(int selectionIndex, X x, Y y, Object jdbcValue, JdbcMapping jdbcMapping); + void consume(int valueIndex, X x, Y y, Object jdbcValue, JdbcMapping jdbcMapping); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ModelPart.java index 136386a9c172..a2ee29128670 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ModelPart.java @@ -123,10 +123,17 @@ void applySqlSelections( DomainResultCreationState creationState, BiConsumer selectionConsumer); + /** + * A short hand form of {@link #forEachSelectable(int, SelectableConsumer)}, that passes 0 as offset. + */ default int forEachSelectable(SelectableConsumer consumer) { return forEachSelectable( 0, consumer ); } + /** + * Visits each selectable mapping with the selectable index offset by the given value. + * Returns the amount of jdbc types that have been visited. + */ default int forEachSelectable(int offset, SelectableConsumer consumer) { return 0; } @@ -135,21 +142,56 @@ default AttributeMapping asAttributeMapping() { return null; } - @FunctionalInterface - interface JdbcValueConsumer { - void consume(Object value, SelectableMapping jdbcValueMapping); - + /** + * A short hand form of {@link #breakDownJdbcValues(Object, int, Object, Object, JdbcValueBiConsumer, SharedSessionContractImplementor)}, + * that passes 0 as offset and null for the two values {@code X} and {@code Y}. + */ + default int breakDownJdbcValues( + Object domainValue, + JdbcValueConsumer valueConsumer, + SharedSessionContractImplementor session) { + return breakDownJdbcValues( domainValue, 0, null, null, valueConsumer, session ); } - void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session); + /** + * Breaks down the domain value to its constituent JDBC values. + * + * Think of it as breaking the multi-dimensional array into a visitable flat array. + * Additionally, it passes through the values {@code X} and {@code Y} to the consumer. + * Returns the amount of jdbc types that have been visited. + */ + int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session); - @FunctionalInterface - interface IndexedJdbcValueConsumer { - void consume(int valueIndex, Object value, SelectableMapping jdbcValueMapping); + /** + * A short hand form of {@link #decompose(Object, int, Object, Object, JdbcValueBiConsumer, SharedSessionContractImplementor)}, + * that passes 0 as offset and null for the two values {@code X} and {@code Y}. + */ + default int decompose( + Object domainValue, + JdbcValueConsumer valueConsumer, + SharedSessionContractImplementor session) { + return decompose( domainValue, 0, null, null, valueConsumer, session ); } - default void decompose(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - breakDownJdbcValues( domainValue, valueConsumer, session ); + /** + * Similar to {@link #breakDownJdbcValues(Object, int, Object, Object, JdbcValueBiConsumer, SharedSessionContractImplementor)}, + * but this method is supposed to be used for decomposing values for assignment expressions. + * Returns the amount of jdbc types that have been visited. + */ + default int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); } EntityMappingType findContainingEntityMapping(); @@ -158,4 +200,31 @@ default boolean areEqual(Object one, Object other, SharedSessionContractImplemen // NOTE : deepEquals to account for arrays (compound natural-id) return Objects.deepEquals( one, other ); } + + /** + * Functional interface for consuming the JDBC values. + */ + @FunctionalInterface + interface JdbcValueConsumer extends JdbcValueBiConsumer { + @Override + default void consume(int valueIndex, Object x, Object y, Object value, SelectableMapping jdbcValueMapping) { + consume( valueIndex, value, jdbcValueMapping ); + } + + /** + * Consume a JDBC-level jdbcValue. The JDBC jdbcMapping descriptor is also passed in + */ + void consume(int valueIndex, Object value, SelectableMapping jdbcValueMapping); + } + + /** + * Functional interface for consuming the JDBC values, along with two values of type {@code X} and {@code Y}. + */ + @FunctionalInterface + interface JdbcValueBiConsumer { + /** + * Consume a JDBC-level jdbcValue. The JDBC jdbcMapping descriptor is also passed in + */ + void consume(int valueIndex, X x, Y y, Object value, SelectableMapping jdbcValueMapping); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java index 3ee008a0b685..2012e9cd0926 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractDiscriminatorMapping.java @@ -239,8 +239,15 @@ public int forEachJdbcType(int offset, IndexedConsumer action) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( disassemble( domainValue, session ), this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, disassemble( domainValue, session ), this ); + return getJdbcTypeCount(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java index 737ada91670d..bac86f4a49a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyDiscriminatorPart.java @@ -225,8 +225,15 @@ public int forEachDisassembledJdbcValue( } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( domainValue, this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, domainValue, this ); + return getJdbcTypeCount(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java index 42034e3cc532..1dda46655ed6 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AnyKeyPart.java @@ -259,8 +259,15 @@ public int forEachSelectable(int offset, SelectableConsumer consumer) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( domainValue, this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, domainValue, this ); + return getJdbcTypeCount(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java index a931fe567101..343433b23dba 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java @@ -423,7 +423,14 @@ public int forEachSelectable(int offset, SelectableConsumer consumer) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( disassemble( domainValue, session ), this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, disassemble( domainValue, session ), this ); + return getJdbcTypeCount(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java index 199df6f646b2..97d09a649db2 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicEntityIdentifierMappingImpl.java @@ -186,8 +186,15 @@ public int forEachSelectable(int offset, SelectableConsumer consumer) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( domainValue, this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, domainValue, this ); + return getJdbcTypeCount(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java index 66c8eff25343..1d8ca9814b73 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java @@ -324,13 +324,26 @@ public int forEachSelectable(int offset, SelectableConsumer consumer) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( disassemble( domainValue, session ), this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, disassemble( domainValue, session ), this ); + return getJdbcTypeCount(); } @Override - public void decompose(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( disassemble( domainValue, session ), this ); + public int decompose( + Object domainValue, int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, disassemble( domainValue, session ), this ); + return getJdbcTypeCount(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java index 028c561bfa62..35f996449d2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java @@ -198,12 +198,19 @@ public void applySqlSelections( @Override public int forEachSelectable(int offset, SelectableConsumer consumer) { consumer.accept( offset, this ); - return 1; + return getJdbcTypeCount(); } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( domainValue, this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, domainValue, this ); + return getJdbcTypeCount(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java index 1ae9cf8dab23..bdb722b19086 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CompoundNaturalIdMapping.java @@ -313,12 +313,19 @@ public void applySqlSelections(NavigablePath navigablePath, TableGroup tableGrou } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + int span = 0; if ( domainValue == null ) { - attributes.forEach( - attributeMapping -> attributeMapping.breakDownJdbcValues( null, valueConsumer, session ) - ); - return; + for ( int i = 0; i < attributes.size(); i++ ) { + span += attributes.get( i ).breakDownJdbcValues( null, offset + span, x, y, valueConsumer, session ); + } + return span; } assert domainValue instanceof Object[]; @@ -327,8 +334,9 @@ public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsu assert values.length == attributes.size(); for ( int i = 0; i < attributes.size(); i++ ) { - attributes.get( i ).breakDownJdbcValues( values[ i ], valueConsumer, session ); + span += attributes.get( i ).breakDownJdbcValues( values[ i ], offset + span, x, y, valueConsumer, session ); } + return span; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java index a1aa1dad9fad..182341fa5f57 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationAttributeMapping.java @@ -323,13 +323,25 @@ public int forEachDisassembledJdbcValue( } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - discriminatorMapping.breakDownJdbcValues( domainValue, valueConsumer, session ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return discriminatorMapping.breakDownJdbcValues( offset, x, y, domainValue, valueConsumer, session ); } @Override - public void decompose(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - discriminatorMapping.decompose( domainValue, valueConsumer, session ); + public int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return discriminatorMapping.decompose( offset, x, y, domainValue, valueConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java index 964a208663c5..414775eec464 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedAssociationMapping.java @@ -206,43 +206,55 @@ public EntityMappingType resolveDiscriminatorValueToEntityMapping(Object discrim return null; } - public void breakDownJdbcValues(Object domainValue, ModelPart.JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { + public int breakDownJdbcValues( + int offset, + X x, + Y y, + Object domainValue, + ModelPart.JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { if ( domainValue == null ) { - valueConsumer.consume( null, getDiscriminatorPart() ); - valueConsumer.consume( null, getKeyPart() ); - return; + valueConsumer.consume( offset, x, y, null, getDiscriminatorPart() ); + valueConsumer.consume( offset + 1, x, y, null, getKeyPart() ); + return getDiscriminatorPart().getJdbcTypeCount() + getKeyPart().getJdbcTypeCount(); } + else { + final EntityMappingType concreteMappingType = determineConcreteType( domainValue, session ); - final EntityMappingType concreteMappingType = determineConcreteType( domainValue, session ); - - final Object discriminator = getModelPart().resolveDiscriminatorForEntityType( concreteMappingType ); - final Object disassembledDiscriminator = getDiscriminatorPart().disassemble( discriminator, session ); - valueConsumer.consume( disassembledDiscriminator, getDiscriminatorPart() ); + final Object discriminator = getModelPart().resolveDiscriminatorForEntityType( concreteMappingType ); + final Object disassembledDiscriminator = getDiscriminatorPart().disassemble( discriminator, session ); + valueConsumer.consume( offset, x, y, disassembledDiscriminator, getDiscriminatorPart() ); - final EntityIdentifierMapping identifierMapping = concreteMappingType.getIdentifierMapping(); - final Object identifier = identifierMapping.getIdentifier( domainValue ); - final Object disassembledKey = getKeyPart().disassemble( identifier, session ); - valueConsumer.consume( disassembledKey, getKeyPart() ); + final EntityIdentifierMapping identifierMapping = concreteMappingType.getIdentifierMapping(); + final Object identifier = identifierMapping.getIdentifier( domainValue ); + final Object disassembledKey = getKeyPart().disassemble( identifier, session ); + valueConsumer.consume( offset + 1, x, y, disassembledKey, getKeyPart() ); + } + return getDiscriminatorPart().getJdbcTypeCount() + getKeyPart().getJdbcTypeCount(); } - public void decompose( + public int decompose( + int offset, + X x, + Y y, Object domainValue, - ModelPart.JdbcValueConsumer valueConsumer, + ModelPart.JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { if ( domainValue == null ) { - valueConsumer.consume( null, getDiscriminatorPart() ); - valueConsumer.consume( null, getKeyPart() ); - return; + valueConsumer.consume( offset, x, y, null, getDiscriminatorPart() ); + valueConsumer.consume( offset + 1, x, y, null, getKeyPart() ); } + else { + final EntityMappingType concreteMappingType = determineConcreteType( domainValue, session ); - final EntityMappingType concreteMappingType = determineConcreteType( domainValue, session ); - - final Object discriminator = getModelPart().resolveDiscriminatorForEntityType( concreteMappingType ); - getDiscriminatorPart().decompose( discriminator, valueConsumer, session ); + final Object discriminator = getModelPart().resolveDiscriminatorForEntityType( concreteMappingType ); + getDiscriminatorPart().decompose( discriminator, offset, x, y, valueConsumer, session ); - final EntityIdentifierMapping identifierMapping = concreteMappingType.getIdentifierMapping(); - final Object identifier = identifierMapping.getIdentifier( domainValue ); - getKeyPart().decompose( identifier, valueConsumer, session ); + final EntityIdentifierMapping identifierMapping = concreteMappingType.getIdentifierMapping(); + final Object identifier = identifierMapping.getIdentifier( domainValue ); + getKeyPart().decompose( identifier, offset + 1, x, y, valueConsumer, session ); + } + return getDiscriminatorPart().getJdbcTypeCount() + getKeyPart().getJdbcTypeCount(); } private EntityMappingType determineConcreteType(Object entity, SharedSessionContractImplementor session) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java index 308902f50611..1040bae7fd23 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/DiscriminatedCollectionPart.java @@ -285,13 +285,25 @@ public int forEachDisassembledJdbcValue( } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - discriminatorMapping.breakDownJdbcValues( domainValue, valueConsumer, session ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return discriminatorMapping.breakDownJdbcValues( offset, x, y, domainValue, valueConsumer, session ); } @Override - public void decompose(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - discriminatorMapping.decompose( domainValue, valueConsumer, session ); + public int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return discriminatorMapping.decompose( offset, x, y, domainValue, valueConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java index b828c794a6ce..d7d57148f652 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddableMappingTypeImpl.java @@ -695,8 +695,15 @@ public int forEachJdbcType(int offset, IndexedConsumer action) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { final int size = attributeMappings.size(); + int span = 0; if ( domainValue instanceof Object[] ) { final Object[] values = (Object[]) domainValue; assert values.length == size; @@ -704,7 +711,14 @@ public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsu for ( int i = 0; i < size; i++ ) { final AttributeMapping attributeMapping = attributeMappings.get( i ); final Object attributeValue = values[ i ]; - attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session ); + span += attributeMapping.breakDownJdbcValues( + attributeValue, + offset + span, + x, + y, + valueConsumer, + session + ); } } else { @@ -713,38 +727,54 @@ public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsu final Object attributeValue = domainValue == null ? null : attributeMapping.getPropertyAccess().getGetter().get( domainValue ); - attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session ); + span += attributeMapping.breakDownJdbcValues( + attributeValue, + offset + span, + x, + y, + valueConsumer, + session + ); } } + return span; } @Override - public void decompose(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { + public int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { if ( shouldBindAggregateMapping() ) { - valueConsumer.consume( domainValue, aggregateMapping ); + valueConsumer.consume( offset, x, y, domainValue, aggregateMapping ); + return 1; } - else if ( domainValue instanceof Object[] ) { + int span = 0; + if ( domainValue instanceof Object[] ) { final Object[] values = (Object[]) domainValue; assert values.length == attributeMappings.size(); for ( int i = 0; i < attributeMappings.size(); i++ ) { final AttributeMapping attributeMapping = attributeMappings.get( i ); final Object attributeValue = values[ i ]; - attributeMapping.decompose( attributeValue, valueConsumer, session ); + span += attributeMapping.decompose( attributeValue, offset + span, x, y, valueConsumer, session ); } } else { - attributeMappings.forEach( (attributeMapping) -> { - if ( attributeMapping instanceof PluralAttributeMapping ) { - return; + for ( int i = 0; i < attributeMappings.size(); i++ ) { + final AttributeMapping attributeMapping = attributeMappings.get( i ); + if ( !(attributeMapping instanceof PluralAttributeMapping )) { + final Object attributeValue = domainValue == null + ? null + : attributeMapping.getPropertyAccess().getGetter().get( domainValue ); + span += attributeMapping.decompose( attributeValue, offset + span, x, y, valueConsumer, session ); } - - final Object attributeValue = domainValue == null - ? null - : attributeMapping.getPropertyAccess().getGetter().get( domainValue ); - attributeMapping.decompose( attributeValue, valueConsumer, session ); - } ); + } } + return span; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java index 191fbf27b059..e44719c815a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java @@ -208,13 +208,25 @@ public void forEachUpdatable(SelectableConsumer consumer) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - getEmbeddableTypeDescriptor().breakDownJdbcValues( domainValue, valueConsumer, session ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return getEmbeddableTypeDescriptor().breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); } @Override - public void decompose(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - getEmbeddableTypeDescriptor().decompose( domainValue, valueConsumer, session ); + public int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return getEmbeddableTypeDescriptor().decompose( domainValue, offset, x, y, valueConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java index 5e3f77f98f7e..a3b96078a69c 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedCollectionPart.java @@ -341,8 +341,14 @@ public int forEachSelectable(int offset, SelectableConsumer consumer) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - getEmbeddableTypeDescriptor().breakDownJdbcValues( domainValue, valueConsumer, session ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return getEmbeddableTypeDescriptor().breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java index f955a8032b68..6cbba5f8a548 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java @@ -532,23 +532,39 @@ public NavigableRole getNavigableRole() { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { if ( domainValue == null ) { - keySelectableMappings.forEachSelectable( (index, selectable) -> { - valueConsumer.consume( null, selectable ); - } ); + final int jdbcTypeCount = keySelectableMappings.getJdbcTypeCount(); + for ( int i = 0; i < jdbcTypeCount; i++ ) { + valueConsumer.consume( offset + i, x, y, null, keySelectableMappings.getSelectable( i ) ); + } + return jdbcTypeCount; } else if ( domainValue instanceof Object[] ) { final Object[] values = (Object[]) domainValue; - keySelectableMappings.forEachSelectable( (index, selectable) -> { - valueConsumer.consume( values[ index ], selectable ); - } ); + final int jdbcTypeCount = keySelectableMappings.getJdbcTypeCount(); + for ( int i = 0; i < jdbcTypeCount; i++ ) { + valueConsumer.consume( offset + i, x, y, values[i], keySelectableMappings.getSelectable( i ) ); + } + return jdbcTypeCount; } else { final MutableInteger columnPosition = new MutableInteger(); - keySide.getModelPart().breakDownJdbcValues( + return keySide.getModelPart().breakDownJdbcValues( domainValue, - (jdbcValue, jdbcValueMapping) -> valueConsumer.consume( + offset, + x, + y, + (valueIndex, arg1, arg2, jdbcValue, jdbcValueMapping) -> valueConsumer.consume( + offset, + arg1, + arg2, jdbcValue, keySelectableMappings.getSelectable( columnPosition.getAndIncrement() ) ), diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java index 3e28a53b88e3..06ff816dc86a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedIdentifierMappingImpl.java @@ -131,8 +131,14 @@ public String getAttributeName() { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - getEmbeddableTypeDescriptor().breakDownJdbcValues( domainValue, valueConsumer, session ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return getEmbeddableTypeDescriptor().breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java index 0a4cf5372509..6f1b12a81bc5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityRowIdMappingImpl.java @@ -154,8 +154,15 @@ public void applySqlSelections( } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( domainValue, this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, domainValue, this ); + return getJdbcTypeCount(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java index 821f08823929..fe356249116a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityVersionMappingImpl.java @@ -290,8 +290,15 @@ public void applySqlSelections( } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( domainValue, this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, domainValue, this ); + return getJdbcTypeCount(); } private SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java index e4d4dac215e0..57614682c0eb 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/IdClassEmbeddable.java @@ -367,11 +367,20 @@ public void forEachSubPart(IndexedConsumer consumer, EntityMappingTyp } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - attributeMappings.forEach( (attribute) -> { + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + int span = 0; + for ( int i = 0; i < attributeMappings.size(); i++ ) { + final AttributeMapping attribute = attributeMappings.get( i ); final Object attributeValue = attribute.getValue( domainValue ); - attribute.breakDownJdbcValues( attributeValue, valueConsumer, session ); - } ); + span += attribute.breakDownJdbcValues( attributeValue, offset + span, x, y, valueConsumer, session ); + } + return span; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java index 79813f3533f6..cfae0436b867 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/InverseNonAggregatedIdentifierMapping.java @@ -262,8 +262,13 @@ public void setIdentifier(Object entity, Object id, SharedSessionContractImpleme } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - identifierValueMapper.breakDownJdbcValues( domainValue, valueConsumer, session ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { + return identifierValueMapper.breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java index 9c2085d171df..133be0696231 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java @@ -137,8 +137,13 @@ public ModelPart findSubPart(String name, EntityMappingType targetType) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - fkTargetModelPart.breakDownJdbcValues( domainValue, valueConsumer, session ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { + return fkTargetModelPart.breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); } @Override @@ -158,9 +163,18 @@ public int forEachSelectable(int offset, SelectableConsumer consumer) { } @Override - public void decompose(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - foreignKey.getKeyPart().decompose( + public int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return foreignKey.getKeyPart().decompose( foreignKey.getAssociationKeyFromSide( domainValue, foreignKey.getTargetSide(), session ), + offset, + x, + y, valueConsumer, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java index 3a55c2011cf0..7cf353e971ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/NonAggregatedIdentifierMappingImpl.java @@ -284,8 +284,13 @@ public void setIdentifier(Object entity, Object id, SharedSessionContractImpleme } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - identifierValueMapper.breakDownJdbcValues( domainValue, valueConsumer, session ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { + return identifierValueMapper.breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/OneToManyCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/OneToManyCollectionPart.java index 19d3fd2b30c8..2396be6a73ab 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/OneToManyCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/OneToManyCollectionPart.java @@ -82,12 +82,18 @@ public Cardinality getCardinality() { } @Override - public void breakDownJdbcValues( + public int breakDownJdbcValues( Object domainValue, - JdbcValueConsumer valueConsumer, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { - getAssociatedEntityMappingType().getIdentifierMapping().breakDownJdbcValues( + return getAssociatedEntityMappingType().getIdentifierMapping().breakDownJdbcValues( disassemble( domainValue, session ), + offset, + x, + y, valueConsumer, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java index b0c4f6c5a2a9..0e7d8f42c5e4 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java @@ -912,7 +912,13 @@ public void applySqlSelections( } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { throw new UnsupportedOperationException(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java index 574adce8d395..68694890b37a 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java @@ -462,12 +462,19 @@ public int forEachDisassembledJdbcValue( JdbcValuesBiConsumer valuesConsumer, SharedSessionContractImplementor session) { valuesConsumer.consume( offset, x, y, value, getJdbcMapping() ); - return 1; + return getJdbcTypeCount(); } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( disassemble( domainValue, session ), keySide.getModelPart() ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, disassemble( domainValue, session ), keySide.getModelPart() ); + return getJdbcTypeCount(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java index aa835d44f069..1f1f322fbdb9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleNaturalIdMapping.java @@ -243,8 +243,14 @@ public Object disassemble(Object value, SharedSessionContractImplementor session } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - attribute.breakDownJdbcValues( domainValue, valueConsumer, session ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + return attribute.breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java index 624001f46d9d..d93541f442ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java @@ -2022,16 +2022,19 @@ public String toString() { } @Override - public void breakDownJdbcValues( + public int breakDownJdbcValues( Object domainValue, - JdbcValueConsumer valueConsumer, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { if ( cardinality == Cardinality.ONE_TO_ONE && sideNature == ForeignKeyDescriptor.Nature.TARGET ) { - return; + return 0; } final Object value = extractValue( domainValue, session ); - foreignKeyDescriptor.breakDownJdbcValues( value, valueConsumer, session ); + return foreignKeyDescriptor.breakDownJdbcValues( value, offset, x, y, valueConsumer, session ); } private Object extractValue(Object domainValue, SharedSessionContractImplementor session) { diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java index 5e5e88576f67..f264be5d5120 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualIdEmbeddable.java @@ -319,29 +319,47 @@ public int forEachJdbcValue( } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - attributeMappings.forEach( (attribute) -> { + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { + int span = 0; + for ( int i = 0; i < attributeMappings.size(); i++ ) { + final AttributeMapping attribute = attributeMappings.get( i ); final Object attributeValue = attribute.getValue( domainValue ); - attribute.breakDownJdbcValues( attributeValue, valueConsumer, session ); - } ); + span += attribute.breakDownJdbcValues( attributeValue, offset + span, x, y, valueConsumer, session ); + } + return span; } @Override - public void decompose(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { + public int decompose( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { if ( idMapping.getIdClassEmbeddable() != null ) { // during decompose, if there is an IdClass for the entity the // incoming `domainValue` should be an instance of that IdClass - idMapping.getIdClassEmbeddable().decompose( domainValue, valueConsumer, session ); + return idMapping.getIdClassEmbeddable().decompose( domainValue, offset, x, y, valueConsumer, session ); } else { + int span = 0; for ( int i = 0; i < attributeMappings.size(); i++ ) { final AttributeMapping attributeMapping = attributeMappings.get( i ); - attributeMapping.decompose( + span += attributeMapping.decompose( attributeMapping.getValue( domainValue ), + offset + span, + x, + y, valueConsumer, session ); } + return span; } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java index f6fb3386b02a..a5d35ef39108 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/BasicCollectionPersister.java @@ -11,6 +11,7 @@ import org.hibernate.cache.CacheException; import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.ParameterUsage; import org.hibernate.engine.jdbc.mutation.internal.MutationQueryOptions; import org.hibernate.engine.spi.SharedSessionContractImplementor; @@ -22,7 +23,6 @@ import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor; import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.collection.mutation.CollectionTableMapping; @@ -331,7 +331,7 @@ private void applyInsertRowValues( Object rowValue, int rowPosition, SharedSessionContractImplementor session, - RowMutationOperations.ValuesBindingConsumer jdbcValueConsumer) { + JdbcValueBindings jdbcValueBindings) { final PluralAttributeMapping attributeMapping = getAttributeMapping(); if ( key == null ) { @@ -339,14 +339,24 @@ private void applyInsertRowValues( } final ForeignKeyDescriptor foreignKey = attributeMapping.getKeyDescriptor(); - foreignKey.getKeyPart().decompose( key, jdbcValueConsumer, session ); + foreignKey.getKeyPart().decompose( + key, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_VALUE_SETTER, + session + ); final MutableInteger columnPositionCount = new MutableInteger(); if ( attributeMapping.getIdentifierDescriptor() != null ) { getAttributeMapping().getIdentifierDescriptor().decompose( collection.getIdentifier( rowValue, rowPosition ), - jdbcValueConsumer, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_VALUE_SETTER, session ); } @@ -361,15 +371,17 @@ else if ( attributeMapping.getIndexDescriptor() != null ) { getAttributeMapping().getIndexDescriptor().decompose( incrementIndexByBase( collection.getIndex( rowValue, rowPosition, this ) ), - (jdbcValue, jdbcValueMapping) -> { + 0, + indexColumnIsSettable, + jdbcValueBindings, + (valueIndex, settable, bindings, jdbcValue, jdbcValueMapping) -> { if ( !jdbcValueMapping.getContainingTableExpression().equals( getTableName() ) ) { // indicates a many-to-many mapping and the index is contained on the // associated entity table - we skip it here return; } - final int columnPosition = columnPositionCount.getAndIncrement(); - if ( indexColumnIsSettable[columnPosition] ) { - jdbcValueConsumer.consume( jdbcValue, jdbcValueMapping ); + if ( settable[valueIndex] ) { + bindings.bindValue( jdbcValue, jdbcValueMapping, ParameterUsage.SET ); } }, session @@ -380,10 +392,12 @@ else if ( attributeMapping.getIndexDescriptor() != null ) { attributeMapping.getElementDescriptor().decompose( collection.getElement( rowValue ), - (jdbcValue, jdbcValueMapping) -> { - final int columnPosition = columnPositionCount.getAndIncrement(); - if ( elementColumnIsSettable[columnPosition] ) { - jdbcValueConsumer.consume( jdbcValue, jdbcValueMapping ); + 0, + elementColumnIsSettable, + jdbcValueBindings, + (valueIndex, settable, bindings, jdbcValue, jdbcValueMapping) -> { + if ( settable[valueIndex] ) { + bindings.bindValue( jdbcValue, jdbcValueMapping, ParameterUsage.SET ); } }, session @@ -477,16 +491,19 @@ private void applyUpdateRowValues( Object entry, int entryPosition, SharedSessionContractImplementor session, - RowMutationOperations.ValuesBindingConsumer jdbcValueConsumer) { + JdbcValueBindings jdbcValueBindings) { final Object element = collection.getElement( entry ); final CollectionPart elementDescriptor = getAttributeMapping().getElementDescriptor(); elementDescriptor.decompose( element, - (jdbcValue, jdbcValueMapping) -> { + 0, + jdbcValueBindings, + null, + (valueIndex, bindings, y, jdbcValue, jdbcValueMapping) -> { if ( !jdbcValueMapping.isUpdateable() || jdbcValueMapping.isFormula() ) { return; } - jdbcValueConsumer.consumeJdbcValueBinding( + bindings.bindValue( jdbcValue, jdbcValueMapping, ParameterUsage.SET @@ -502,29 +519,53 @@ private void applyUpdateRowRestrictions( Object entry, int entryPosition, SharedSessionContractImplementor session, - ModelPart.JdbcValueConsumer restrictor) { + JdbcValueBindings jdbcValueBindings) { if ( getAttributeMapping().getIdentifierDescriptor() != null ) { final CollectionIdentifierDescriptor identifierDescriptor = getAttributeMapping().getIdentifierDescriptor(); final Object identifier = collection.getIdentifier( entry, entryPosition ); - identifierDescriptor.decompose( identifier, restrictor, session ); + identifierDescriptor.decompose( + identifier, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, + session + ); } else { - getAttributeMapping().getKeyDescriptor().getKeyPart().decompose( key, restrictor, session ); + getAttributeMapping().getKeyDescriptor().getKeyPart().decompose( + key, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, + session + ); if ( getAttributeMapping().getIndexDescriptor() != null && !indexContainsFormula ) { final Object index = collection.getIndex( entry, entryPosition, getAttributeMapping().getCollectionDescriptor() ); final Object adjustedIndex = incrementIndexByBase( index ); - getAttributeMapping().getIndexDescriptor().decompose( adjustedIndex, restrictor, session ); + getAttributeMapping().getIndexDescriptor().decompose( + adjustedIndex, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, + session + ); } else { final Object snapshotElement = collection.getSnapshotElement( entry, entryPosition ); getAttributeMapping().getElementDescriptor().decompose( snapshotElement, - (jdbcValue, jdbcValueMapping) -> { + 0, + jdbcValueBindings, + null, + (valueIndex, bindings, noop, jdbcValue, jdbcValueMapping) -> { if ( jdbcValueMapping.isNullable() || jdbcValueMapping.isFormula() ) { return; } - restrictor.consume( jdbcValue, jdbcValueMapping ); + bindings.bindValue( jdbcValue, jdbcValueMapping, ParameterUsage.RESTRICT ); }, session ); @@ -616,30 +657,50 @@ private void applyDeleteRowRestrictions( Object rowValue, int rowPosition, SharedSessionContractImplementor session, - ModelPart.JdbcValueConsumer restrictor) { + JdbcValueBindings jdbcValueBindings) { final PluralAttributeMapping attributeMapping = getAttributeMapping(); if ( attributeMapping.getIdentifierDescriptor() != null ) { - attributeMapping.getIdentifierDescriptor().decompose( rowValue, restrictor, session ); + attributeMapping.getIdentifierDescriptor().decompose( + rowValue, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, + session + ); } else { - getAttributeMapping().getKeyDescriptor().getKeyPart().decompose( keyValue, restrictor, session ); + getAttributeMapping().getKeyDescriptor().getKeyPart().decompose( + keyValue, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, + session + ); if ( hasPhysicalIndexColumn() ) { attributeMapping.getIndexDescriptor().decompose( incrementIndexByBase( rowValue ), - restrictor, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, session ); } else { attributeMapping.getElementDescriptor().decompose( rowValue, - (jdbcValue, jdbcValueMapping) -> { + 0, + jdbcValueBindings, + null, + (valueIndex, bindings, noop, jdbcValue, jdbcValueMapping) -> { if ( jdbcValueMapping.isNullable() || jdbcValueMapping.isFormula() ) { return; } - restrictor.consume( jdbcValue, jdbcValueMapping ); + bindings.bindValue( jdbcValue, jdbcValueMapping, ParameterUsage.RESTRICT ); }, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java index 9fd03751b38e..233a1799d408 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/OneToManyPersister.java @@ -31,7 +31,6 @@ import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; -import org.hibernate.metamodel.mapping.ModelPart.JdbcValueConsumer; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; @@ -243,11 +242,7 @@ private void writeIndex( entry, nextIndex, session, - (jdbcValue, jdbcValueMapping, usage) -> jdbcValueBindings.bindValue( - jdbcValue, - jdbcValueMapping, - usage - ) + jdbcValueBindings ); updateRowRestrictions.applyRestrictions( @@ -256,11 +251,7 @@ private void writeIndex( entry, nextIndex, session, - (jdbcValue, jdbcValueMapping) -> jdbcValueBindings.bindValue( - jdbcValue, - jdbcValueMapping, - ParameterUsage.RESTRICT - ) + jdbcValueBindings ); mutationExecutor.execute( collection, null, null, null, session ); @@ -621,10 +612,24 @@ private void applyDeleteRowRestrictions( Object rowValue, int rowPosition, SharedSessionContractImplementor session, - JdbcValueConsumer jdbcValueConsumer) { + JdbcValueBindings jdbcValueBindings) { final PluralAttributeMapping pluralAttribute = getAttributeMapping(); - pluralAttribute.getKeyDescriptor().decompose( keyValue, jdbcValueConsumer, session ); - pluralAttribute.getElementDescriptor().decompose( rowValue, jdbcValueConsumer, session ); + pluralAttribute.getKeyDescriptor().decompose( + keyValue, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, + session + ); + pluralAttribute.getElementDescriptor().decompose( + rowValue, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, + session + ); } @@ -658,21 +663,31 @@ private void applyInsertRowValues( Object rowValue, int rowPosition, SharedSessionContractImplementor session, - RowMutationOperations.ValuesBindingConsumer bindingsConsumer) { + JdbcValueBindings jdbcValueBindings) { final PluralAttributeMapping attributeMapping = getAttributeMapping(); - attributeMapping.getKeyDescriptor().getKeyPart().decompose( keyValue, bindingsConsumer, session ); + attributeMapping.getKeyDescriptor().getKeyPart().decompose( + keyValue, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_VALUE_SETTER, + session + ); final CollectionPart indexDescriptor = attributeMapping.getIndexDescriptor(); if ( indexDescriptor != null ) { indexDescriptor.decompose( incrementIndexByBase( collection.getIndex( rowValue, rowPosition, this ) ), - (value, jdbcValueMapping) -> { + 0, + jdbcValueBindings, + null, + (valueIndex, bindings, noop, value, jdbcValueMapping) -> { if ( !jdbcValueMapping.isUpdateable() ) { return; } - bindingsConsumer.consume( value, jdbcValueMapping ); + bindings.bindValue( value, jdbcValueMapping, ParameterUsage.SET ); }, session ); @@ -683,11 +698,10 @@ private void applyInsertRowValues( final EntityIdentifierMapping identifierMapping = elementDescriptor.getAssociatedEntityMappingType().getIdentifierMapping(); identifierMapping.decompose( identifierMapping.getIdentifier( elementValue ), - (jdbcValue, jdbcValueMapping) -> bindingsConsumer.consumeJdbcValueBinding( - jdbcValue, - jdbcValueMapping, - ParameterUsage.RESTRICT - ), + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, session ); } @@ -754,16 +768,19 @@ private void applyWriteIndexValues( Object entry, int entryPosition, SharedSessionContractImplementor session, - RowMutationOperations.ValuesBindingConsumer bindingsConsumer) { + JdbcValueBindings jdbcValueBindings) { final Object index = collection.getIndex( entry, entryPosition, this ); getAttributeMapping().getIndexDescriptor().decompose( index, - (jdbcValue, jdbcValueMapping) -> { + 0, + jdbcValueBindings, + null, + (valueIndex, bindings, noop, jdbcValue, jdbcValueMapping) -> { if ( !jdbcValueMapping.isUpdateable() ) { return; } - bindingsConsumer.consume( jdbcValue, jdbcValueMapping ); + bindings.bindValue( jdbcValue, jdbcValueMapping, ParameterUsage.SET ); }, session ); @@ -775,16 +792,30 @@ private void applyWriteIndexRestrictions( Object entry, int entryPosition, SharedSessionContractImplementor session, - JdbcValueConsumer jdbcValueConsumer) { + JdbcValueBindings jdbcValueBindings) { final OneToManyCollectionPart elementDescriptor = (OneToManyCollectionPart) getAttributeMapping().getElementDescriptor(); final EntityMappingType associatedType = elementDescriptor.getAssociatedEntityMappingType(); final Object element = collection.getElement( entry ); final Object elementIdentifier = associatedType.getIdentifierMapping().getIdentifier( element ); - associatedType.getIdentifierMapping().decompose( elementIdentifier, jdbcValueConsumer, session ); + associatedType.getIdentifierMapping().decompose( + elementIdentifier, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, + session + ); if ( getAttributeMapping().getIdentifierDescriptor() != null ) { final Object identifier = collection.getIdentifier( entry, entryPosition ); - getAttributeMapping().getIdentifierDescriptor().decompose( identifier, jdbcValueConsumer, session ); + getAttributeMapping().getIdentifierDescriptor().decompose( + identifier, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, + session + ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/DeleteRowsCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/DeleteRowsCoordinatorStandard.java index b8cfb6905259..ba798a8a61b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/DeleteRowsCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/DeleteRowsCoordinatorStandard.java @@ -100,16 +100,7 @@ public void deleteRows(PersistentCollection collection, Object key, SharedSes removal, deletionCount, session, - (jdbcValue, jdbcValueMapping) -> { - if ( jdbcValueMapping.isFormula() ) { - return; - } - jdbcValueBindings.bindValue( - jdbcValue, - jdbcValueMapping, - ParameterUsage.RESTRICT - ); - } + jdbcValueBindings ); mutationExecutor.execute( removal, null, null, null, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/InsertRowsCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/InsertRowsCoordinatorStandard.java index 63d287ea133d..6d79c4e14d7a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/InsertRowsCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/InsertRowsCoordinatorStandard.java @@ -12,6 +12,7 @@ import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.MutationExecutor; +import org.hibernate.engine.jdbc.mutation.ParameterUsage; import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.PluralAttributeMapping; @@ -112,7 +113,7 @@ public void insertRows( entry, entryCount, session, - jdbcValueBindings::bindValue + jdbcValueBindings ); mutationExecutor.execute( entry, null, null, null, session ); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/RemoveCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/RemoveCoordinatorStandard.java index 5ba26efa2261..5d331f019d0f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/RemoveCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/RemoveCoordinatorStandard.java @@ -9,7 +9,6 @@ import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.MutationExecutor; -import org.hibernate.engine.jdbc.mutation.ParameterUsage; import org.hibernate.engine.jdbc.mutation.spi.MutationExecutorService; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; @@ -100,13 +99,10 @@ public void deleteAllRows(Object key, SharedSessionContractImplementor session) final ForeignKeyDescriptor fkDescriptor = mutationTarget.getTargetPart().getKeyDescriptor(); fkDescriptor.getKeyPart().decompose( key, - (jdbcValue, jdbcValueMapping) -> { - jdbcValueBindings.bindValue( - jdbcValue, - jdbcValueMapping, - ParameterUsage.RESTRICT - ); - }, + 0, + jdbcValueBindings, + null, + RowMutationOperations.DEFAULT_RESTRICTOR, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/RowMutationOperations.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/RowMutationOperations.java index c60171367574..59a5fc1626c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/RowMutationOperations.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/RowMutationOperations.java @@ -7,11 +7,11 @@ package org.hibernate.persister.collection.mutation; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.jdbc.mutation.JdbcValueBindings; import org.hibernate.engine.jdbc.mutation.ParameterUsage; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.NullnessHelper; -import org.hibernate.metamodel.mapping.ModelPart.JdbcValueConsumer; -import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.ast.MutatingTableReference; import org.hibernate.sql.model.jdbc.JdbcMutationOperation; @@ -25,6 +25,12 @@ * @author Steve Ebersole */ public class RowMutationOperations { + public static final ModelPart.JdbcValueBiConsumer DEFAULT_RESTRICTOR = (valueIndex, jdbcValueBindings, o, value, jdbcValueMapping) -> { + jdbcValueBindings.bindValue( value, jdbcValueMapping, ParameterUsage.RESTRICT ); + }; + public static final ModelPart.JdbcValueBiConsumer DEFAULT_VALUE_SETTER = (valueIndex, jdbcValueBindings, o, value, jdbcValueMapping) -> { + jdbcValueBindings.bindValue( value, jdbcValueMapping, ParameterUsage.SET ); + }; private final CollectionMutationTarget target; private final OperationProducer insertRowOperationProducer; @@ -163,7 +169,7 @@ void applyRestrictions( Object rowValue, int rowPosition, SharedSessionContractImplementor session, - JdbcValueConsumer restrictor); + JdbcValueBindings jdbcValueBindings); } @FunctionalInterface @@ -174,23 +180,7 @@ void applyValues( Object rowValue, int rowPosition, SharedSessionContractImplementor session, - ValuesBindingConsumer valuesBindingConsumer); + JdbcValueBindings jdbcValueBindings); } - /** - * Consumer for insert-value bindings - * - * Unfortunate we need `usage` here, but it is needed to account for - * update-as-insert handling of one-to-many handling - */ - @FunctionalInterface - public interface ValuesBindingConsumer extends JdbcValueConsumer { - void consumeJdbcValueBinding(Object value, SelectableMapping jdbcValueMapping, ParameterUsage usage); - - @Override - default void consume(Object value, SelectableMapping jdbcValueMapping) { - // insert values will be SET most of the time - consumeJdbcValueBinding( value, jdbcValueMapping, ParameterUsage.SET ); - } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/UpdateRowsCoordinatorOneToMany.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/UpdateRowsCoordinatorOneToMany.java index 413f05d6ebb9..4c8e4648e682 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/UpdateRowsCoordinatorOneToMany.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/UpdateRowsCoordinatorOneToMany.java @@ -88,11 +88,7 @@ private void deleteRows(Object key, PersistentCollection collection, SharedSe entry, entryPosition, session, - (jdbcValue, jdbcValueMapping) -> jdbcValueBindings.bindValue( - jdbcValue, - jdbcValueMapping, - ParameterUsage.RESTRICT - ) + jdbcValueBindings ); mutationExecutor.execute( entry, null, null, null, session ); @@ -150,11 +146,7 @@ private int insertRows(Object key, PersistentCollection collection, SharedSes entry, entryPosition, session, - (jdbcValue, jdbcValueMapping, usage) -> jdbcValueBindings.bindValue( - jdbcValue, - jdbcValueMapping, - usage - ) + jdbcValueBindings ); mutationExecutor.execute( entry, null, null, null, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/UpdateRowsCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/UpdateRowsCoordinatorStandard.java index aab195d137d8..35f3cef3a6db 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/UpdateRowsCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/mutation/UpdateRowsCoordinatorStandard.java @@ -126,11 +126,7 @@ private boolean processRow( entry, entryPosition, session, - (jdbcValue, jdbcValueMapping, usage) -> mutationExecutor.getJdbcValueBindings().bindValue( - jdbcValue, - jdbcValueMapping, - usage - ) + mutationExecutor.getJdbcValueBindings() ); rowMutationOperations.getUpdateRowRestrictions().applyRestrictions( @@ -139,11 +135,7 @@ private boolean processRow( entry, entryPosition, session, - (jdbcValue, jdbcValueMapping) -> mutationExecutor.getJdbcValueBindings().bindValue( - jdbcValue, - jdbcValueMapping, - ParameterUsage.RESTRICT - ) + mutationExecutor.getJdbcValueBindings() ); mutationExecutor.execute( collection, null, null, null, session ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index fb7d945a0f27..0b9abeafeafb 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -490,21 +490,36 @@ default AttributeMapping getAttributeMapping(int position) { } @Override - default void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { + default int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + int span = 0; if ( domainValue instanceof Object[] ) { final Object[] values = (Object[]) domainValue; for ( int i = 0; i < getNumberOfAttributeMappings(); i++ ) { final AttributeMapping attributeMapping = getAttributeMapping( i ); - attributeMapping.breakDownJdbcValues( values[ i ], valueConsumer, session ); + span += attributeMapping.breakDownJdbcValues( values[ i ], offset + span, x, y, valueConsumer, session ); } } else { for ( int i = 0; i < getNumberOfAttributeMappings(); i++ ) { final AttributeMapping attributeMapping = getAttributeMapping( i ); final Object attributeValue = attributeMapping.getPropertyAccess().getGetter().get( domainValue ); - attributeMapping.breakDownJdbcValues( attributeValue, valueConsumer, session ); + span += attributeMapping.breakDownJdbcValues( + attributeValue, + offset + span, + x, + y, + valueConsumer, + session + ); } } + return span; } /** diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java index c8b91556c95b..dc8f0d1c8c54 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/AbstractMutationCoordinator.java @@ -16,6 +16,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.persister.entity.AbstractEntityPersister; +import org.hibernate.persister.entity.AttributeMappingsList; import org.hibernate.sql.model.ModelMutationLogging; import org.hibernate.sql.model.MutationOperation; import org.hibernate.sql.model.MutationOperationGroup; @@ -112,26 +113,31 @@ protected void bindPartitionColumnValueBindings( Object[] loadedState, SharedSessionContractImplementor session, JdbcValueBindings jdbcValueBindings) { - if ( entityPersister().hasPartitionedSelectionMapping() ) { - entityPersister().forEachAttributeMapping( - (index, attributeMapping) -> { - if ( attributeMapping.hasPartitionedSelectionMapping() ) { - attributeMapping.decompose( - loadedState[index], - (value, jdbcValueMapping) -> { - if ( jdbcValueMapping.isPartitioned() ) { - jdbcValueBindings.bindValue( - value, - jdbcValueMapping, - ParameterUsage.RESTRICT - ); - } - }, - session - ); - } - } - ); + final AbstractEntityPersister persister = entityPersister(); + if ( persister.hasPartitionedSelectionMapping() ) { + final AttributeMappingsList attributeMappings = persister.getAttributeMappings(); + final int size = attributeMappings.size(); + for ( int i = 0; i < size; i++ ) { + final AttributeMapping attributeMapping = attributeMappings.get( i ); + if ( attributeMapping.hasPartitionedSelectionMapping() ) { + attributeMapping.decompose( + loadedState[i], + 0, + jdbcValueBindings, + null, + (valueIndex, bindings, noop, value, jdbcValueMapping) -> { + if ( jdbcValueMapping.isPartitioned() ) { + bindings.bindValue( + value, + jdbcValueMapping, + ParameterUsage.RESTRICT + ); + } + }, + session + ); + } + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/DeleteCoordinator.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/DeleteCoordinator.java index 68340372c2a3..7b1914d6e353 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/DeleteCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/DeleteCoordinator.java @@ -170,14 +170,17 @@ private void applyAllOrDirtyLocking( final String mutationTableName = persister.getAttributeMutationTableName( attributeIndex ); attribute.breakDownJdbcValues( loadedValue, - (jdbcValue, jdbcValueMapping) -> { + 0, + jdbcValueBindings, + mutationTableName, + (valueIndex, bindings, tableName, jdbcValue, jdbcValueMapping) -> { if ( jdbcValue == null ) { // presumably the SQL was generated with `is null` return; } - jdbcValueBindings.bindValue( + bindings.bindValue( jdbcValue, - mutationTableName, + tableName, jdbcValueMapping.getSelectionExpression(), ParameterUsage.RESTRICT ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java index 6996d1ccb5a2..2d6f559fd615 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java @@ -243,9 +243,12 @@ private void decomposeAttribute( if ( !(mapping instanceof PluralAttributeMapping) ) { mapping.decompose( value, - (jdbcValue, selectableMapping) -> { + 0, + jdbcValueBindings, + null, + (valueIndex, bindings, noop, jdbcValue, selectableMapping) -> { if ( selectableMapping.isInsertable() ) { - jdbcValueBindings.bindValue( + bindings.bindValue( jdbcValue, entityPersister().physicalTableNameForMutation( selectableMapping ), selectableMapping.getSelectionExpression(), diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java index d4f6a1345d01..d18ccc80e23b 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java @@ -675,10 +675,13 @@ private void processLock( Object attributeLockValue) { attributeMapping.decompose( attributeLockValue, - (jdbcValue, columnMapping) -> { + 0, + analysis, + null, + (valueIndex, updateAnalysis, noop, jdbcValue, columnMapping) -> { if ( !columnMapping.isFormula() ) { final EntityTableMapping tableMapping = entityPersister().getPhysicalTableMappingForMutation( columnMapping ); - analysis.registerColumnOptLock( tableMapping, columnMapping.getSelectionExpression(), jdbcValue ); + updateAnalysis.registerColumnOptLock( tableMapping, columnMapping.getSelectionExpression(), jdbcValue ); } }, session @@ -854,11 +857,14 @@ private static void decomposeAttributeMapping( Object values) { attributeMapping.decompose( values, - (jdbcValue, jdbcMapping) -> { + 0, + jdbcValueBindings, + tableMapping, + (valueIndex, bindings, table, jdbcValue, jdbcMapping) -> { if ( !jdbcMapping.isFormula() && jdbcMapping.isUpdateable() ) { - jdbcValueBindings.bindValue( + bindings.bindValue( jdbcValue, - tableMapping.getTableName(), + table.getTableName(), jdbcMapping.getSelectionExpression(), ParameterUsage.SET ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java index f805ad67d191..1dbe79e3c4a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleBasicValuedModelPart.java @@ -298,8 +298,15 @@ public int forEachJdbcType(int offset, IndexedConsumer action) { } @Override - public void breakDownJdbcValues(Object domainValue, JdbcValueConsumer valueConsumer, SharedSessionContractImplementor session) { - valueConsumer.consume( domainValue, this ); + public int breakDownJdbcValues( + Object domainValue, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, + SharedSessionContractImplementor session) { + valueConsumer.consume( offset, x, y, domainValue, this ); + return getJdbcTypeCount(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java index a86efa703a8d..e2112718a033 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEmbeddableValuedModelPart.java @@ -401,19 +401,23 @@ public void applySqlSelections( } @Override - public void breakDownJdbcValues( + public int breakDownJdbcValues( Object domainValue, - JdbcValueConsumer valueConsumer, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { final Object[] values = (Object[]) domainValue; assert values.length == modelParts.size(); - + int span = 0; int i = 0; for ( ModelPart mapping : modelParts.values() ) { final Object attributeValue = values[ i ]; - mapping.breakDownJdbcValues( attributeValue, valueConsumer, session ); + span += mapping.breakDownJdbcValues( attributeValue, offset + span, x, y, valueConsumer, session ); i++; } + return span; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java index b6b4b24555ea..ef272f252d25 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleEntityValuedModelPart.java @@ -550,11 +550,14 @@ public void applySqlSelections( } @Override - public void breakDownJdbcValues( + public int breakDownJdbcValues( Object domainValue, - JdbcValueConsumer valueConsumer, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { - delegate.breakDownJdbcValues( domainValue, valueConsumer, session ); + return delegate.breakDownJdbcValues( domainValue, offset, x, y, valueConsumer, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleTableGroupProducer.java b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleTableGroupProducer.java index 3cd91b1717bb..4151ac0f0eec 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleTableGroupProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/derived/AnonymousTupleTableGroupProducer.java @@ -374,9 +374,12 @@ public void applySqlSelections( } @Override - public void breakDownJdbcValues( + public int breakDownJdbcValues( Object domainValue, - JdbcValueConsumer valueConsumer, + int offset, + X x, + Y y, + JdbcValueBiConsumer valueConsumer, SharedSessionContractImplementor session) { throw new UnsupportedOperationException( "Not yet implemented" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/ColumnValueBindingList.java b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/ColumnValueBindingList.java index e9941b2e3b30..3c930fb0537f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/model/ast/ColumnValueBindingList.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/model/ast/ColumnValueBindingList.java @@ -40,7 +40,7 @@ public Object clone() { } @Override - public void consume(Object value, SelectableMapping jdbcValueMapping) { + public void consume(int valueIndex, Object value, SelectableMapping jdbcValueMapping) { final ColumnValueBinding columnValueBinding = createValueBinding( jdbcValueMapping.getSelectionExpression(), value == null ? null : jdbcValueMapping.getWriteExpression(), diff --git a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/internal/ValidityAuditStrategy.java b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/internal/ValidityAuditStrategy.java index 4d2d76397518..5f57f1c0c385 100644 --- a/hibernate-envers/src/main/java/org/hibernate/envers/strategy/internal/ValidityAuditStrategy.java +++ b/hibernate-envers/src/main/java/org/hibernate/envers/strategy/internal/ValidityAuditStrategy.java @@ -33,7 +33,6 @@ import org.hibernate.envers.internal.entities.mapper.relation.MiddleIdData; import org.hibernate.envers.internal.revisioninfo.RevisionInfoNumberReader; import org.hibernate.envers.internal.synchronization.SessionCacheCleaner; -import org.hibernate.envers.internal.tools.MutableInteger; import org.hibernate.envers.internal.tools.query.Parameters; import org.hibernate.envers.internal.tools.query.QueryBuilder; import org.hibernate.envers.strategy.AuditStrategy; @@ -714,34 +713,45 @@ public int bind( int index, PreparedStatement statement, SessionImplementor session) { - final MutableInteger position = new MutableInteger( index ); - modelPart.breakDownJdbcValues( - value, - (jdbcValue, jdbcValueMapping) -> { - try { - //noinspection unchecked - jdbcValueMapping.getJdbcMapping().getJdbcValueBinder().bind( - statement, - jdbcValue, - position.getAndIncrease(), - session - ); - } - catch (SQLException e) { - throw session.getJdbcServices().getSqlExceptionHelper().convert( - e, - String.format( - Locale.ROOT, - "Error binding JDBC value relative to `%s`", - modelPart.getNavigableRole().getFullPath() - ) - ); - } - }, - session - ); + try { + return modelPart.breakDownJdbcValues( + value, + index, + statement, + session, + (valueIndex, preparedStatement, sessionImplementor, jdbcValue, jdbcValueMapping) -> { + try { + //noinspection unchecked + jdbcValueMapping.getJdbcMapping().getJdbcValueBinder().bind( + preparedStatement, + jdbcValue, + valueIndex, + sessionImplementor + ); + } + catch (SQLException e) { + throw new NestedRuntimeException( e ); + } + }, + session + ); + } + catch (NestedRuntimeException e) { + throw session.getJdbcServices().getSqlExceptionHelper().convert( + (SQLException) e.getCause(), + String.format( + Locale.ROOT, + "Error binding JDBC value relative to `%s`", + modelPart.getNavigableRole().getFullPath() + ) + ); + } + } - return modelPart.getJdbcTypeCount(); + static class NestedRuntimeException extends RuntimeException { + public NestedRuntimeException(SQLException cause) { + super( cause ); + } } } } From 4d433e7c0323787d51b432d972272cd25bb90bbe Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Fri, 10 Feb 2023 14:53:26 +0100 Subject: [PATCH 0075/1497] HHH-15990 Add test for issue --- .../notfound/NotFoundAndSelectJoinTest.java | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/notfound/NotFoundAndSelectJoinTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/NotFoundAndSelectJoinTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/NotFoundAndSelectJoinTest.java new file mode 100644 index 000000000000..7705cd45ae5b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/NotFoundAndSelectJoinTest.java @@ -0,0 +1,165 @@ +package org.hibernate.orm.test.notfound; + +import java.util.List; + +import org.hibernate.Hibernate; +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Tuple; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + NotFoundAndSelectJoinTest.Person.class, + NotFoundAndSelectJoinTest.Address.class, + } +) +@SessionFactory(useCollectingStatementInspector = true) +@TestForIssue(jiraKey = "HHH-15990") +public class NotFoundAndSelectJoinTest { + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Address address = new Address( 1l, "Texas", "Austin" ); + Person person = new Person( 2l, "And", address ); + + session.persist( address ); + session.persist( person ); + } + ); + } + + @Test + public void testQuery(SessionFactoryScope scope) { + SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector(); + statementInspector.clear(); + scope.inTransaction( + session -> { + List
addresses = session.createQuery( + "select a from Address a where a.state = :state", + Address.class + ) + .setParameter( "state", "Texas" ).list(); + + assertThat( addresses.size() ).isEqualTo( 1 ); + Address address = addresses.get( 0 ); + Person person = address.getPerson(); + assertThat( person ).isNotNull(); + assertThat( Hibernate.isInitialized( person ) ); + + assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 1 ); + assertThat( statementInspector.getNumberOfJoins( 0 ) ).isEqualTo( 1 ); + } + ); + } + + @Test + public void testQueryWithJoin(SessionFactoryScope scope) { + SQLStatementInspector statementInspector = (SQLStatementInspector) scope.getStatementInspector(); + statementInspector.clear(); + scope.inTransaction( + session -> { + List tuples = session.createQuery( + "select a, p from Address a left join a.person p where a.state = :state", + Tuple.class + ) + .setParameter( "state", "Texas" ).list(); + + assertThat( tuples.size() ).isEqualTo( 1 ); + Tuple tuple = tuples.get( 0 ); + Address address = tuple.get( 0, Address.class ); + + Person person = address.getPerson(); + assertThat( person ).isNotNull(); + assertThat( Hibernate.isInitialized( person ) ); + + Person p = tuple.get( 1, Person.class ); + assertThat( p ).isEqualTo( person ); + assertThat( p.getName() ).isEqualTo( "And" ); + + assertThat( statementInspector.getSqlQueries().size() ).isEqualTo( 1 ); + assertThat( statementInspector.getNumberOfJoins( 0 ) ).isEqualTo( 1 ); + + } + ); + } + + @Entity(name = "Address") + public static class Address { + @Id + private Long id; + + @ManyToOne + @NotFound(action = NotFoundAction.IGNORE) + private Person person; + + private String state; + private String city; + + public Address() { + } + + public Address(Long id, String state, String city) { + this.id = id; + this.state = state; + this.city = city; + } + + public Long getId() { + return id; + } + + public Person getPerson() { + return person; + } + + public String getState() { + return state; + } + + public String getCity() { + return city; + } + } + + @Entity(name = "Person") + public static class Person { + @Id + private Long id; + + private String name; + + public Person() { + } + + public Person(Long id, String name, Address address) { + this.id = id; + this.name = name; + address.person = this; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + } +} From c6e30f8042c0995a555518c118ccb1b2bfc248eb Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Sun, 12 Feb 2023 10:13:18 +0100 Subject: [PATCH 0076/1497] HHH-15990 unable to determine TableReference when associate ManyToOne fetch lazy and NotFound IGNORE --- .../org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a1e55317678f..0ff335a54abd 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 @@ -3769,7 +3769,7 @@ private Expression visitTableGroup(TableGroup tableGroup, SqmFrom path) { if ( inferredEntityMapping == null ) { // When the inferred mapping is null, we try to resolve to the FK by default, // which is fine because expansion to all target columns for select and group by clauses is handled below - if ( entityValuedModelPart instanceof EntityAssociationMapping && ( (EntityAssociationMapping) entityValuedModelPart ).getSideNature() != ForeignKeyDescriptor.Nature.TARGET ) { + if ( entityValuedModelPart instanceof EntityAssociationMapping && ( (EntityAssociationMapping) entityValuedModelPart ).isFkOptimizationAllowed() ) { // If the table group uses an association mapping that is not a one-to-many, // we make use of the FK model part final EntityAssociationMapping associationMapping = (EntityAssociationMapping) entityValuedModelPart; From 078c5bdc412701ca7a95633855326aadfde58c6f Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 7 Feb 2023 13:14:06 +0100 Subject: [PATCH 0077/1497] HHH-16109 Add test for issue --- .../jpa/criteria/query/NamedQueryTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/NamedQueryTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/NamedQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/NamedQueryTest.java new file mode 100644 index 000000000000..98d11a8395ea --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/criteria/query/NamedQueryTest.java @@ -0,0 +1,107 @@ +/* + * 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.orm.test.jpa.criteria.query; + +import org.hibernate.query.sqm.internal.QuerySqmImpl; + +import org.hibernate.testing.orm.domain.gambit.BasicEntity; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = BasicEntity.class) +@JiraKey("HHH-16109") +public class NamedQueryTest { + @BeforeAll + public void prepare(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.persist( new BasicEntity( 1, "test_1" ) ); + session.persist( new BasicEntity( 2, "test_2" ) ); + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery criteria = cb.createQuery( BasicEntity.class ); + criteria.select( criteria.from( BasicEntity.class ) ); + // Criteria + final TypedQuery criteriaQuery = session.createQuery( criteria ); + scope.getSessionFactory().addNamedQuery( "criteria_query", criteriaQuery ); + // Criteria + limit / offset + final TypedQuery criteriaQueryLimit = session.createQuery( criteria ); + criteriaQueryLimit.setFirstResult( 1 ).setMaxResults( 1 ); + scope.getSessionFactory().addNamedQuery( "criteria_query_limit", criteriaQueryLimit ); + // HQL + final TypedQuery hqlQuery = session.createQuery( "from BasicEntity", BasicEntity.class ); + scope.getSessionFactory().addNamedQuery( "hql_query", hqlQuery ); + // HQL + limit / offset + final TypedQuery hqlQueryLimit = session.createQuery( "from BasicEntity", BasicEntity.class ); + hqlQueryLimit.setFirstResult( 1 ).setMaxResults( 1 ); + scope.getSessionFactory().addNamedQuery( "hql_query_limit", hqlQueryLimit ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from BasicEntity" ).executeUpdate() ); + } + + @Test + public void testCriteria(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TypedQuery query = session.createNamedQuery( "criteria_query", BasicEntity.class ); + final QuerySqmImpl querySqm = (QuerySqmImpl) query; + assertNull( querySqm.getQueryOptions().getLimit().getFirstRow() ); + assertNull( querySqm.getQueryOptions().getLimit().getMaxRows() ); + assertEquals( 2, query.getResultList().size() ); + } ); + } + + @Test + public void testCriteriaLimit(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TypedQuery query = session.createNamedQuery( "criteria_query_limit", BasicEntity.class ); + final QuerySqmImpl querySqm = (QuerySqmImpl) query; + assertEquals( 1, querySqm.getQueryOptions().getLimit().getFirstRow() ); + assertEquals( 1, querySqm.getQueryOptions().getLimit().getMaxRows() ); + assertEquals( 1, query.getResultList().size() ); + } ); + } + + @Test + public void testHql(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TypedQuery query = session.createNamedQuery( "hql_query", BasicEntity.class ); + final QuerySqmImpl querySqm = (QuerySqmImpl) query; + assertNull( querySqm.getQueryOptions().getLimit().getFirstRow() ); + assertNull( querySqm.getQueryOptions().getLimit().getMaxRows() ); + assertEquals( 2, query.getResultList().size() ); + } ); + } + + @Test + public void testHqlLimit(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final TypedQuery query = session.createNamedQuery( "hql_query_limit", BasicEntity.class ); + final QuerySqmImpl querySqm = (QuerySqmImpl) query; + assertEquals( 1, querySqm.getQueryOptions().getLimit().getFirstRow() ); + assertEquals( 1, querySqm.getQueryOptions().getLimit().getMaxRows() ); + assertEquals( 1, query.getResultList().size() ); + } ); + } +} From 21a837fda0d526551bf5ec32b8b7d991170af32f Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 7 Feb 2023 14:24:08 +0100 Subject: [PATCH 0078/1497] HHH-16109 Don't add limits to named queries when not needed --- .../org/hibernate/query/sqm/internal/QuerySqmImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java index 9f0482d44616..f0479291ae04 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/QuerySqmImpl.java @@ -1128,8 +1128,8 @@ public NamedQueryMemento toMemento(String name) { return new NamedCriteriaQueryMementoImpl( name, sqmStatement, - getFirstResult(), - getMaxResults(), + getQueryOptions().getLimit().getFirstRow(), + getQueryOptions().getLimit().getMaxRows(), isCacheable(), getCacheRegion(), getCacheMode(), @@ -1147,8 +1147,8 @@ public NamedQueryMemento toMemento(String name) { return new NamedHqlQueryMementoImpl( name, getQueryString(), - getFirstResult(), - getMaxResults(), + getQueryOptions().getLimit().getFirstRow(), + getQueryOptions().getLimit().getMaxRows(), isCacheable(), getCacheRegion(), getCacheMode(), From ec778bc632166fb0d85b22dd5587791440f5f701 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Mon, 13 Feb 2023 16:52:04 +0100 Subject: [PATCH 0079/1497] Replace mockito for some tests with custom spies --- .../test/agroal/AgroalSkipAutoCommitTest.java | 18 +- ...reparedStatementSpyConnectionProvider.java | 39 +- .../BaseInsertOrderingTest.java | 23 +- .../jdbc/internal/AggressiveReleaseTest.java | 2 - .../jdbc/internal/SessionJdbcBatchTest.java | 48 +- ...tementIsClosedAfterALockExceptionTest.java | 4 +- .../StoreProcedureStatementsClosedTest.java | 2 - .../orm/test/query/QueryTimeOutTest.java | 74 ++- .../query/criteria/CriteriaTimeoutTest.java | 33 +- .../AbstractSkipAutoCommitTest.java | 31 +- .../timestamp/JdbcTimeCustomTimeZoneTest.java | 40 +- .../JdbcTimeDefaultTimeZoneTest.java | 22 +- ...mestampCustomSessionLevelTimeZoneTest.java | 40 +- .../JdbcTimestampCustomTimeZoneTest.java | 40 +- .../JdbcTimestampDefaultTimeZoneTest.java | 28 +- ...reparedStatementSpyConnectionProvider.java | 45 +- .../hikaricp/HikariCPSkipAutoCommitTest.java | 19 +- .../org/hibernate/testing/jdbc/JdbcSpies.java | 578 ++++++++++++++++++ ...reparedStatementSpyConnectionProvider.java | 111 +--- ...tSpyConnectionProviderSettingProvider.java | 2 +- 20 files changed, 803 insertions(+), 396 deletions(-) create mode 100644 hibernate-testing/src/main/java/org/hibernate/testing/jdbc/JdbcSpies.java diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java index 44df1a49f9c1..de5209d48172 100644 --- a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/AgroalSkipAutoCommitTest.java @@ -9,28 +9,21 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; import org.hibernate.test.agroal.util.PreparedStatementSpyConnectionProvider; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; import jakarta.persistence.Entity; import jakarta.persistence.Id; import java.sql.Connection; -import java.sql.SQLException; import java.util.List; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; /** * @author Vlad Mihalcea */ -@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class AgroalSkipAutoCommitTest extends BaseCoreFunctionalTestCase { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); @@ -74,12 +67,15 @@ private void verifyConnections() { List connections = connectionProvider.getReleasedConnections(); assertEquals( 1, connections.size() ); - Connection connection = connections.get( 0 ); try { - verify(connection, never()).setAutoCommit( false ); + List setAutoCommitCalls = connectionProvider.spyContext.getCalls( + Connection.class.getMethod( "setAutoCommit", boolean.class ), + connections.get( 0 ) + ); + assertTrue( "setAutoCommit should never be called", setAutoCommitCalls.isEmpty() ); } - catch (SQLException e) { - fail(e.getMessage()); + catch (NoSuchMethodException e) { + throw new RuntimeException( e ); } } diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java index 1f9025f42a44..5c86333f8d38 100644 --- a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java @@ -9,18 +9,13 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.sql.Statement; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import org.hibernate.agroal.internal.AgroalConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; -import org.mockito.internal.util.MockUtil; +import org.hibernate.testing.jdbc.JdbcSpies; /** * This {@link ConnectionProvider} extends any other ConnectionProvider that would be used by default taken the current configuration properties, and it @@ -29,8 +24,7 @@ * @author Vlad Mihalcea */ public class PreparedStatementSpyConnectionProvider extends AgroalConnectionProvider { - - private final Map preparedStatementMap = new LinkedHashMap<>(); + public final JdbcSpies.SpyContext spyContext = new JdbcSpies.SpyContext(); private final List acquiredConnections = new ArrayList<>( ); private final List releasedConnections = new ArrayList<>( ); @@ -53,7 +47,7 @@ public Connection getConnection() throws SQLException { public void closeConnection(Connection conn) throws SQLException { acquiredConnections.remove( conn ); releasedConnections.add( conn ); - super.closeConnection( (Connection) MockUtil.getMockSettings( conn ).getSpiedInstance() ); + super.closeConnection( spyContext.getSpiedInstance( conn ) ); } @Override @@ -63,29 +57,7 @@ public void stop() { } private Connection spy(Connection connection) { - if ( MockUtil.isMock( connection ) ) { - return connection; - } - Connection connectionSpy = Mockito.spy( connection ); - try { - Mockito.doAnswer( invocation -> { - PreparedStatement statement = (PreparedStatement) invocation.callRealMethod(); - PreparedStatement statementSpy = Mockito.spy( statement ); - String sql = (String) invocation.getArguments()[0]; - preparedStatementMap.put( statementSpy, sql ); - return statementSpy; - } ).when( connectionSpy ).prepareStatement( ArgumentMatchers.anyString() ); - - Mockito.doAnswer( invocation -> { - Statement statement = (Statement) invocation.callRealMethod(); - Statement statementSpy = Mockito.spy( statement ); - return statementSpy; - } ).when( connectionSpy ).createStatement(); - } - catch ( SQLException e ) { - throw new IllegalArgumentException( e ); - } - return connectionSpy; + return JdbcSpies.spy( connection, spyContext ); } /** @@ -94,8 +66,7 @@ private Connection spy(Connection connection) { public void clear() { acquiredConnections.clear(); releasedConnections.clear(); - preparedStatementMap.keySet().forEach( Mockito::reset ); - preparedStatementMap.clear(); + spyContext.clear(); } /** diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/insertordering/BaseInsertOrderingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/insertordering/BaseInsertOrderingTest.java index ee08ecc3bf45..24107528e35e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/insertordering/BaseInsertOrderingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/insertordering/BaseInsertOrderingTest.java @@ -1,8 +1,8 @@ package org.hibernate.orm.test.insertordering; import java.sql.PreparedStatement; -import java.sql.SQLException; import java.sql.Types; +import java.util.List; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; @@ -14,18 +14,13 @@ import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest; -import org.hibernate.testing.orm.junit.DialectFeatureChecks; -import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.junit.jupiter.api.AfterAll; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; /** * @author Nathan Xu */ -@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJdbcDriverProxying.class) abstract class BaseInsertOrderingTest extends BaseSessionFactoryFunctionalTest { static class Batch { @@ -43,8 +38,6 @@ static class Batch { } private final PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( - true, - false ); @Override @@ -80,10 +73,18 @@ void verifyContainsBatches(Batch... expectedBatches) { for ( Batch expectedBatch : expectedBatches ) { PreparedStatement preparedStatement = connectionProvider.getPreparedStatement( expectedBatch.sql ); try { - verify( preparedStatement, times( expectedBatch.size ) ).addBatch(); - verify( preparedStatement, times( 1 ) ).executeBatch(); + List addBatchCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "addBatch" ), + preparedStatement + ); + List executeBatchCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "executeBatch" ), + preparedStatement + ); + assertThat( addBatchCalls.size() ).isEqualTo( expectedBatch.size ); + assertThat( executeBatchCalls.size() ).isEqualTo( 1 ); } - catch (SQLException e) { + catch (Exception e) { throw new RuntimeException( e ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/AggressiveReleaseTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/AggressiveReleaseTest.java index b67210358482..001e6dd24258 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/AggressiveReleaseTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/AggressiveReleaseTest.java @@ -39,8 +39,6 @@ public class AggressiveReleaseTest extends BaseSessionFactoryFunctionalTest { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( - false, - false, true ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/SessionJdbcBatchTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/SessionJdbcBatchTest.java index eef6d7239a51..bf92535f89bc 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/SessionJdbcBatchTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/internal/SessionJdbcBatchTest.java @@ -7,7 +7,6 @@ package org.hibernate.orm.test.jdbc.internal; import java.sql.PreparedStatement; -import java.sql.SQLException; import java.util.List; import java.util.Map; import jakarta.persistence.Entity; @@ -17,24 +16,19 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; /** * @author Vlad Mihalcea */ -@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) public class SessionJdbcBatchTest extends BaseNonConfigCoreFunctionalTestCase { - private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( true, false ); + private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); @Override protected Class[] getAnnotatedClasses() { @@ -77,7 +71,7 @@ protected void cleanupTestData() throws Exception { private long id; @Test - public void testSessionFactorySetting() throws SQLException { + public void testSessionFactorySetting() throws Throwable { Session session = sessionFactory().openSession(); session.beginTransaction(); try { @@ -90,13 +84,21 @@ public void testSessionFactorySetting() throws SQLException { } PreparedStatement preparedStatement = connectionProvider.getPreparedStatement( "insert into Event (name,id) values (?,?)" ); - verify( preparedStatement, times( 5 ) ).addBatch(); - verify( preparedStatement, times( 3 ) ).executeBatch(); + List addBatchCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "addBatch" ), + preparedStatement + ); + List executeBatchCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "executeBatch" ), + preparedStatement + ); + assertEquals( 5, addBatchCalls.size() ); + assertEquals( 3, executeBatchCalls.size() ); } @Test public void testSessionSettingOverridesSessionFactorySetting() - throws SQLException { + throws Throwable { Session session = sessionFactory().openSession(); session.setJdbcBatchSize( 3 ); session.beginTransaction(); @@ -110,8 +112,16 @@ public void testSessionSettingOverridesSessionFactorySetting() } PreparedStatement preparedStatement = connectionProvider.getPreparedStatement( "insert into Event (name,id) values (?,?)" ); - verify(preparedStatement, times( 5 )).addBatch(); - verify(preparedStatement, times( 2 )).executeBatch(); + List addBatchCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "addBatch" ), + preparedStatement + ); + List executeBatchCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "executeBatch" ), + preparedStatement + ); + assertEquals( 5, addBatchCalls.size() ); + assertEquals( 2, executeBatchCalls.size() ); session = sessionFactory().openSession(); session.setJdbcBatchSize( null ); @@ -127,8 +137,16 @@ public void testSessionSettingOverridesSessionFactorySetting() List preparedStatements = connectionProvider.getPreparedStatements(); assertEquals(1, preparedStatements.size()); preparedStatement = preparedStatements.get( 0 ); - verify(preparedStatement, times( 5 )).addBatch(); - verify(preparedStatement, times( 3 )).executeBatch(); + addBatchCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "addBatch" ), + preparedStatement + ); + executeBatchCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "executeBatch" ), + preparedStatement + ); + assertEquals( 5, addBatchCalls.size() ); + assertEquals( 3, executeBatchCalls.size() ); } private void addEvents(Session session) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/StatementIsClosedAfterALockExceptionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/StatementIsClosedAfterALockExceptionTest.java index 802010da2f94..94aaa9967ff6 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/StatementIsClosedAfterALockExceptionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/lock/StatementIsClosedAfterALockExceptionTest.java @@ -32,11 +32,11 @@ /** * @author Andrea Boriero */ -@RequiresDialectFeature({DialectChecks.SupportsJdbcDriverProxying.class, DialectChecks.SupportsLockTimeouts.class}) +@RequiresDialectFeature({DialectChecks.SupportsLockTimeouts.class}) @SkipForDialect(value = CockroachDialect.class, comment = "for update clause does not imply locking. See https://github.com/cockroachdb/cockroach/issues/88995") public class StatementIsClosedAfterALockExceptionTest extends BaseEntityManagerFunctionalTestCase { - private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider( false, false ); + private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider(); private Integer lockId; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/StoreProcedureStatementsClosedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/StoreProcedureStatementsClosedTest.java index 3f8f0b1ef3fc..b96d93547638 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/StoreProcedureStatementsClosedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/procedure/StoreProcedureStatementsClosedTest.java @@ -27,8 +27,6 @@ public class StoreProcedureStatementsClosedTest extends BaseSessionFactoryFunctionalTest { private final PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( - true, - false ); @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/QueryTimeOutTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/QueryTimeOutTest.java index a417bc9bd7ef..7eeae547aff4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/QueryTimeOutTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/QueryTimeOutTest.java @@ -6,8 +6,9 @@ */ package org.hibernate.orm.test.query; -import java.sql.SQLException; +import java.sql.Statement; import java.sql.Types; +import java.util.List; import java.util.Map; import org.hibernate.cfg.AvailableSettings; @@ -34,8 +35,7 @@ import static org.hibernate.jpa.SpecHints.HINT_SPEC_QUERY_TIMEOUT; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.fail; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author Gail Badner @@ -44,8 +44,6 @@ public class QueryTimeOutTest extends BaseNonConfigCoreFunctionalTestCase { private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider( - true, - false ); private static final String QUERY = "update AnEntity set name='abc'"; @@ -93,12 +91,15 @@ public void testCreateQuerySetTimeout() { query.executeUpdate(); try { - verify( - CONNECTION_PROVIDER.getPreparedStatement( expectedSqlQuery ), - times( 1 ) - ).setQueryTimeout( 123 ); + List setQueryTimeoutCalls = CONNECTION_PROVIDER.spyContext.getCalls( + Statement.class.getMethod( "setQueryTimeout", int.class ), + CONNECTION_PROVIDER.getPreparedStatement( expectedSqlQuery ) + ); + assertEquals( 2, setQueryTimeoutCalls.size() ); + assertEquals( 123, setQueryTimeoutCalls.get( 0 )[0] ); + assertEquals( 0, setQueryTimeoutCalls.get( 1 )[0] ); } - catch (SQLException ex) { + catch (Exception ex) { fail( "should not have thrown exception" ); } } @@ -115,12 +116,15 @@ public void testCreateQuerySetTimeoutHint() { query.executeUpdate(); try { - verify( - CONNECTION_PROVIDER.getPreparedStatement( expectedSqlQuery ), - times( 1 ) - ).setQueryTimeout( 123 ); + List setQueryTimeoutCalls = CONNECTION_PROVIDER.spyContext.getCalls( + Statement.class.getMethod( "setQueryTimeout", int.class ), + CONNECTION_PROVIDER.getPreparedStatement( expectedSqlQuery ) + ); + assertEquals( 2, setQueryTimeoutCalls.size() ); + assertEquals( 123, setQueryTimeoutCalls.get( 0 )[0] ); + assertEquals( 0, setQueryTimeoutCalls.get( 1 )[0] ); } - catch (SQLException ex) { + catch (Exception ex) { fail( "should not have thrown exception" ); } } @@ -137,9 +141,15 @@ public void testCreateNativeQuerySetTimeout() { query.executeUpdate(); try { - verify( CONNECTION_PROVIDER.getPreparedStatement( QUERY ), times( 1 ) ).setQueryTimeout( 123 ); + List setQueryTimeoutCalls = CONNECTION_PROVIDER.spyContext.getCalls( + Statement.class.getMethod( "setQueryTimeout", int.class ), + CONNECTION_PROVIDER.getPreparedStatement( QUERY ) + ); + assertEquals( 2, setQueryTimeoutCalls.size() ); + assertEquals( 123, setQueryTimeoutCalls.get( 0 )[0] ); + assertEquals( 0, setQueryTimeoutCalls.get( 1 )[0] ); } - catch (SQLException ex) { + catch (Exception ex) { fail( "should not have thrown exception" ); } } @@ -156,9 +166,15 @@ public void testCreateNativeQuerySetTimeoutHint() { query.executeUpdate(); try { - verify( CONNECTION_PROVIDER.getPreparedStatement( QUERY ), times( 1 ) ).setQueryTimeout( 123 ); + List setQueryTimeoutCalls = CONNECTION_PROVIDER.spyContext.getCalls( + Statement.class.getMethod( "setQueryTimeout", int.class ), + CONNECTION_PROVIDER.getPreparedStatement( QUERY ) + ); + assertEquals( 2, setQueryTimeoutCalls.size() ); + assertEquals( 123, setQueryTimeoutCalls.get( 0 )[0] ); + assertEquals( 0, setQueryTimeoutCalls.get( 1 )[0] ); } - catch (SQLException ex) { + catch (Exception ex) { fail( "should not have thrown exception" ); } } @@ -175,9 +191,15 @@ public void testCreateSQLQuerySetTimeout() { query.executeUpdate(); try { - verify( CONNECTION_PROVIDER.getPreparedStatement( QUERY ), times( 1 ) ).setQueryTimeout( 123 ); + List setQueryTimeoutCalls = CONNECTION_PROVIDER.spyContext.getCalls( + Statement.class.getMethod( "setQueryTimeout", int.class ), + CONNECTION_PROVIDER.getPreparedStatement( QUERY ) + ); + assertEquals( 2, setQueryTimeoutCalls.size() ); + assertEquals( 123, setQueryTimeoutCalls.get( 0 )[0] ); + assertEquals( 0, setQueryTimeoutCalls.get( 1 )[0] ); } - catch (SQLException ex) { + catch (Exception ex) { fail( "should not have thrown exception" ); } } @@ -194,9 +216,15 @@ public void testCreateSQLQuerySetTimeoutHint() { query.executeUpdate(); try { - verify( CONNECTION_PROVIDER.getPreparedStatement( QUERY ), times( 1 ) ).setQueryTimeout( 123 ); + List setQueryTimeoutCalls = CONNECTION_PROVIDER.spyContext.getCalls( + Statement.class.getMethod( "setQueryTimeout", int.class ), + CONNECTION_PROVIDER.getPreparedStatement( QUERY ) + ); + assertEquals( 2, setQueryTimeoutCalls.size() ); + assertEquals( 123, setQueryTimeoutCalls.get( 0 )[0] ); + assertEquals( 0, setQueryTimeoutCalls.get( 1 )[0] ); } - catch (SQLException ex) { + catch (Exception ex) { fail( "should not have thrown exception" ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaTimeoutTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaTimeoutTest.java index 3e24f30f2bd4..bfb2e2c40205 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaTimeoutTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/criteria/CriteriaTimeoutTest.java @@ -13,7 +13,8 @@ */ package org.hibernate.orm.test.query.criteria; -import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; import java.util.Map; import org.hibernate.Session; @@ -24,6 +25,7 @@ import org.hibernate.query.sqm.tree.select.SqmSelectStatement; import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; +import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProviderSettingProvider; import org.hibernate.testing.orm.junit.DialectFeatureChecks; import org.hibernate.testing.orm.junit.EntityManagerFactoryScope; import org.hibernate.testing.orm.junit.JiraKey; @@ -31,6 +33,7 @@ import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.Setting; import org.hibernate.testing.orm.junit.SettingProvider; +import org.junit.Assert; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -44,9 +47,7 @@ import jakarta.persistence.criteria.CriteriaQuery; import jakarta.persistence.criteria.CriteriaUpdate; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -58,7 +59,7 @@ settingProviders = { @SettingProvider( settingName = AvailableSettings.CONNECTION_PROVIDER, - provider = CriteriaTimeoutTest.SpyConnectionProviderSettingProvider.class) + provider = PreparedStatementSpyConnectionProviderSettingProvider.class) } ) @RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJdbcDriverProxying.class) @@ -150,13 +151,16 @@ public void testCreateMutationQueryCriteriaInsertSelect(EntityManagerFactoryScop private void verifyQuerySetTimeoutWasCalled() { try { - verify( - connectionProvider.getPreparedStatements().get( 0 ), - times( 1 ) - ).setQueryTimeout( 123 ); + List setQueryTimeoutCalls = connectionProvider.spyContext.getCalls( + Statement.class.getMethod( "setQueryTimeout", int.class ), + connectionProvider.getPreparedStatements().get( 0 ) + ); + assertEquals( 2, setQueryTimeoutCalls.size() ); + assertEquals( 123, setQueryTimeoutCalls.get( 0 )[0] ); + assertEquals( 0, setQueryTimeoutCalls.get( 1 )[0] ); } - catch (SQLException e) { - fail( "should not have thrown exception" ); + catch (Exception ex) { + Assert.fail( "should not have thrown exception" ); } } @@ -182,11 +186,4 @@ public void setName(String name) { } } - public static class SpyConnectionProviderSettingProvider - implements SettingProvider.Provider { - @Override - public PreparedStatementSpyConnectionProvider getSetting() { - return new PreparedStatementSpyConnectionProvider( true, false ); - } - } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java index c1a186e26220..767bae16c086 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/resource/transaction/jdbc/autocommit/AbstractSkipAutoCommitTest.java @@ -10,27 +10,23 @@ import java.sql.SQLException; import java.util.List; import java.util.Map; -import jakarta.persistence.Entity; -import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.Id; import javax.sql.DataSource; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Dialect; +import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.orm.junit.DialectContext; import org.hibernate.testing.orm.junit.DialectFeatureChecks; -import org.hibernate.testing.orm.junit.RequiresDialectFeature; -import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.orm.junit.EntityManagerFactoryBasedFunctionalTest; - +import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.junit.jupiter.api.Test; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; /** * @author Vlad Mihalcea @@ -39,7 +35,7 @@ public abstract class AbstractSkipAutoCommitTest extends EntityManagerFactoryBasedFunctionalTest { private PreparedStatementSpyConnectionProvider connectionProvider = - new PreparedStatementSpyConnectionProvider( false, true ) { + new PreparedStatementSpyConnectionProvider() { @Override protected Connection actualConnection() throws SQLException { Connection connection = super.actualConnection(); @@ -84,7 +80,7 @@ protected Dialect getDialect() { } @Test - public void test() { + public void test() throws Throwable { inTransaction( entityManager -> { // Moved inside the transaction because the new base class defers the EMF creation w/ respect to the @@ -113,18 +109,17 @@ public void test() { verifyConnections(); } - private void verifyConnections() { + private void verifyConnections() throws Throwable { assertTrue( connectionProvider.getAcquiredConnections().isEmpty() ); List connections = connectionProvider.getReleasedConnections(); assertEquals( 1, connections.size() ); Connection connection = connections.get( 0 ); - try { - verify(connection, never()).setAutoCommit( false ); - } - catch (SQLException e) { - fail(e.getMessage()); - } + List setAutoCommitCalls = connectionProvider.spyContext.getCalls( + Connection.class.getMethod( "setAutoCommit", boolean.class ), + connection + ); + assertEquals( 0, setAutoCommitCalls.size() ); } @Entity(name = "City" ) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimeCustomTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimeCustomTimeZoneTest.java index 1870ad6ccd92..106f2c7e6a4b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimeCustomTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimeCustomTimeZoneTest.java @@ -8,12 +8,12 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLException; import java.sql.Statement; import java.sql.Time; import java.time.Instant; import java.time.OffsetTime; import java.util.Calendar; +import java.util.List; import java.util.TimeZone; import java.util.concurrent.TimeUnit; @@ -24,34 +24,23 @@ import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest; -import org.hibernate.testing.orm.junit.DialectFeatureChecks; -import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import org.mockito.ArgumentCaptor; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; /** * @author Vlad Mihalcea */ @SkipForDialect(dialectClass = MySQLDialect.class, matchSubTypes = true) -@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJdbcDriverProxying.class) public class JdbcTimeCustomTimeZoneTest extends BaseSessionFactoryFunctionalTest { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( - true, - false ); private static final TimeZone TIME_ZONE = TimeZone.getTimeZone( @@ -84,7 +73,7 @@ protected void releaseResources() { } @Test - public void testTimeZone() { + public void testTimeZone() throws Throwable { connectionProvider.clear(); inTransaction( s -> { @@ -97,22 +86,15 @@ public void testTimeZone() { assertEquals( 1, connectionProvider.getPreparedStatements().size() ); PreparedStatement ps = connectionProvider.getPreparedStatements() .get( 0 ); - try { - ArgumentCaptor calendarArgumentCaptor = ArgumentCaptor.forClass( - Calendar.class ); - verify( ps, times( 1 ) ).setTime( - anyInt(), - any( Time.class ), - calendarArgumentCaptor.capture() - ); - assertEquals( - TIME_ZONE, - calendarArgumentCaptor.getValue().getTimeZone() - ); - } - catch (SQLException e) { - fail( e.getMessage() ); - } + List setTimeCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "setTime", int.class, Time.class, Calendar.class ), + ps + ); + assertEquals( 1, setTimeCalls.size() ); + assertEquals( + TIME_ZONE, + ( (Calendar) setTimeCalls.get( 0 )[2] ).getTimeZone() + ); connectionProvider.clear(); inTransaction( s -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimeDefaultTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimeDefaultTimeZoneTest.java index 2f36c52779fe..a1f1715bd0ea 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimeDefaultTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimeDefaultTimeZoneTest.java @@ -7,8 +7,8 @@ package org.hibernate.orm.test.timestamp; import java.sql.PreparedStatement; -import java.sql.SQLException; import java.sql.Time; +import java.util.List; import java.util.concurrent.TimeUnit; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; @@ -26,11 +26,6 @@ import jakarta.persistence.Id; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; /** * @author Vlad Mihalcea @@ -40,8 +35,6 @@ public class JdbcTimeDefaultTimeZoneTest extends BaseSessionFactoryFunctionalTest { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( - true, - false ); @Override @@ -67,7 +60,7 @@ protected void releaseResources() { } @Test - public void testTimeZone() { + public void testTimeZone() throws Throwable { connectionProvider.clear(); inTransaction( s -> { @@ -80,12 +73,11 @@ public void testTimeZone() { assertEquals( 1, connectionProvider.getPreparedStatements().size() ); PreparedStatement ps = connectionProvider.getPreparedStatements() .get( 0 ); - try { - verify( ps, times( 1 ) ).setTime( anyInt(), any( Time.class ) ); - } - catch (SQLException e) { - fail( e.getMessage() ); - } + List setTimeCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "setTime", int.class, Time.class ), + ps + ); + assertEquals( 1, setTimeCalls.size() ); inTransaction( s -> { Person person = s.find( Person.class, 1L ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampCustomSessionLevelTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampCustomSessionLevelTimeZoneTest.java index 163445b69243..8a0030eab9dc 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampCustomSessionLevelTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampCustomSessionLevelTimeZoneTest.java @@ -8,10 +8,10 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.util.Calendar; +import java.util.List; import java.util.TimeZone; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; @@ -21,32 +21,23 @@ import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest; -import org.hibernate.testing.orm.junit.DialectFeatureChecks; -import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import org.mockito.ArgumentCaptor; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; /** * @author Vlad Mihalcea */ @SkipForDialect(dialectClass = MySQLDialect.class, matchSubTypes = true) -@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJdbcDriverProxying.class) public class JdbcTimestampCustomSessionLevelTimeZoneTest extends BaseSessionFactoryFunctionalTest { - private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( true, false ); + private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(); private static final TimeZone TIME_ZONE = TimeZone.getTimeZone( "America/Los_Angeles" ); @@ -73,7 +64,7 @@ protected void releaseResources() { } @Test - public void testTimeZone() { + public void testTimeZone() throws Throwable { connectionProvider.clear(); doInHibernateSessionBuilder( () -> { @@ -88,22 +79,15 @@ public void testTimeZone() { assertEquals( 1, connectionProvider.getPreparedStatements().size() ); PreparedStatement ps = connectionProvider.getPreparedStatements() .get( 0 ); - try { - ArgumentCaptor calendarArgumentCaptor = ArgumentCaptor.forClass( - Calendar.class ); - verify( ps, times( 1 ) ).setTimestamp( - anyInt(), - any( Timestamp.class ), - calendarArgumentCaptor.capture() - ); - assertEquals( - TIME_ZONE, - calendarArgumentCaptor.getValue().getTimeZone() - ); - } - catch ( SQLException e ) { - fail( e.getMessage() ); - } + List setTimeCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "setTimestamp", int.class, Timestamp.class, Calendar.class ), + ps + ); + assertEquals( 1, setTimeCalls.size() ); + assertEquals( + TIME_ZONE, + ( (Calendar) setTimeCalls.get( 0 )[2] ).getTimeZone() + ); connectionProvider.clear(); doInHibernateSessionBuilder( () -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampCustomTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampCustomTimeZoneTest.java index 5618e5efa4d0..d6e044244ea7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampCustomTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampCustomTimeZoneTest.java @@ -8,10 +8,10 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.util.Calendar; +import java.util.List; import java.util.TimeZone; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; @@ -21,34 +21,23 @@ import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest; -import org.hibernate.testing.orm.junit.DialectFeatureChecks; -import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.hibernate.testing.orm.junit.SkipForDialect; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import org.mockito.ArgumentCaptor; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; /** * @author Vlad Mihalcea */ @SkipForDialect(dialectClass = MySQLDialect.class, matchSubTypes = true) -@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJdbcDriverProxying.class) public class JdbcTimestampCustomTimeZoneTest extends BaseSessionFactoryFunctionalTest { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( - true, - false ); private static final TimeZone TIME_ZONE = TimeZone.getTimeZone( @@ -81,7 +70,7 @@ protected void releaseResources() { } @Test - public void testTimeZone() { + public void testTimeZone() throws Throwable { connectionProvider.clear(); inTransaction( s -> { @@ -94,22 +83,15 @@ public void testTimeZone() { assertEquals( 1, connectionProvider.getPreparedStatements().size() ); PreparedStatement ps = connectionProvider.getPreparedStatements() .get( 0 ); - try { - ArgumentCaptor calendarArgumentCaptor = ArgumentCaptor.forClass( - Calendar.class ); - verify( ps, times( 1 ) ).setTimestamp( - anyInt(), - any( Timestamp.class ), - calendarArgumentCaptor.capture() - ); - assertEquals( - TIME_ZONE, - calendarArgumentCaptor.getValue().getTimeZone() - ); - } - catch (SQLException e) { - fail( e.getMessage() ); - } + List setTimeCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "setTimestamp", int.class, Timestamp.class, Calendar.class ), + ps + ); + assertEquals( 1, setTimeCalls.size() ); + assertEquals( + TIME_ZONE, + ( (Calendar) setTimeCalls.get( 0 )[2] ).getTimeZone() + ); connectionProvider.clear(); inTransaction( s -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampDefaultTimeZoneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampDefaultTimeZoneTest.java index dabcaad70d50..ad4595a4fa9f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampDefaultTimeZoneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/timestamp/JdbcTimestampDefaultTimeZoneTest.java @@ -7,8 +7,8 @@ package org.hibernate.orm.test.timestamp; import java.sql.PreparedStatement; -import java.sql.SQLException; import java.sql.Timestamp; +import java.util.List; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.cfg.AvailableSettings; @@ -16,8 +16,6 @@ import org.hibernate.testing.orm.jdbc.PreparedStatementSpyConnectionProvider; import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest; -import org.hibernate.testing.orm.junit.DialectFeatureChecks; -import org.hibernate.testing.orm.junit.RequiresDialectFeature; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; @@ -25,22 +23,14 @@ import jakarta.persistence.Id; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; /** * @author Vlad Mihalcea */ -@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsJdbcDriverProxying.class) public class JdbcTimestampDefaultTimeZoneTest extends BaseSessionFactoryFunctionalTest { private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( - true, - false ); @Override @@ -66,7 +56,7 @@ protected void releaseResources() { } @Test - public void testTimeZone() { + public void testTimeZone() throws Throwable { connectionProvider.clear(); inTransaction( s -> { @@ -79,15 +69,11 @@ public void testTimeZone() { assertEquals( 1, connectionProvider.getPreparedStatements().size() ); PreparedStatement ps = connectionProvider.getPreparedStatements() .get( 0 ); - try { - verify( ps, times( 1 ) ).setTimestamp( - anyInt(), - any( Timestamp.class ) - ); - } - catch (SQLException e) { - fail( e.getMessage() ); - } + List setTimeCalls = connectionProvider.spyContext.getCalls( + PreparedStatement.class.getMethod( "setTimestamp", int.class, Timestamp.class ), + ps + ); + assertEquals( 1, setTimeCalls.size() ); inTransaction( s -> { Person person = s.find( Person.class, 1L ); diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/orm/test/util/PreparedStatementSpyConnectionProvider.java b/hibernate-hikaricp/src/test/java/org/hibernate/orm/test/util/PreparedStatementSpyConnectionProvider.java index 1eee39de5522..1fa1c3300de2 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/orm/test/util/PreparedStatementSpyConnectionProvider.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/orm/test/util/PreparedStatementSpyConnectionProvider.java @@ -9,21 +9,14 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.sql.Statement; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.concurrent.LinkedBlockingQueue; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.testing.jdbc.ConnectionProviderDelegate; +import org.hibernate.testing.jdbc.JdbcSpies; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; -import org.mockito.internal.util.MockUtil; /** * This {@link ConnectionProvider} extends any other ConnectionProvider that would be used by default taken the current configuration properties, and it @@ -33,11 +26,7 @@ */ public class PreparedStatementSpyConnectionProvider extends ConnectionProviderDelegate { - - // We must keep around the mocked connections, otherwise the are garbage collected and trigger finalizers - // Since we use CALLS_REAL_METHODS this might close underlying IO resources which make other objects unusable - private static final Queue MOCKS = new LinkedBlockingQueue<>(); - private final Map preparedStatementMap = new LinkedHashMap<>(); + public final JdbcSpies.SpyContext spyContext = new JdbcSpies.SpyContext(); private final List acquiredConnections = new ArrayList<>( ); private final List releasedConnections = new ArrayList<>( ); @@ -52,7 +41,6 @@ protected Connection actualConnection() throws SQLException { @Override public Connection getConnection() throws SQLException { Connection connection = spy( actualConnection() ); - MOCKS.add( connection ); acquiredConnections.add( connection ); return connection; } @@ -61,7 +49,7 @@ public Connection getConnection() throws SQLException { public void closeConnection(Connection conn) throws SQLException { acquiredConnections.remove( conn ); releasedConnections.add( conn ); - super.closeConnection( (Connection) MockUtil.getMockSettings( conn ).getSpiedInstance() ); + super.closeConnection( spyContext.getSpiedInstance( conn ) ); } @Override @@ -71,29 +59,7 @@ public void stop() { } private Connection spy(Connection connection) { - if ( MockUtil.isMock( connection ) ) { - return connection; - } - Connection connectionSpy = Mockito.spy( connection ); - try { - Mockito.doAnswer( invocation -> { - PreparedStatement statement = (PreparedStatement) invocation.callRealMethod(); - PreparedStatement statementSpy = Mockito.spy( statement ); - String sql = (String) invocation.getArguments()[0]; - preparedStatementMap.put( statementSpy, sql ); - return statementSpy; - } ).when( connectionSpy ).prepareStatement( ArgumentMatchers.anyString() ); - - Mockito.doAnswer( invocation -> { - Statement statement = (Statement) invocation.callRealMethod(); - Statement statementSpy = Mockito.spy( statement ); - return statementSpy; - } ).when( connectionSpy ).createStatement(); - } - catch ( SQLException e ) { - throw new IllegalArgumentException( e ); - } - return connectionSpy; + return JdbcSpies.spy( connection, spyContext ); } /** @@ -102,8 +68,7 @@ private Connection spy(Connection connection) { public void clear() { acquiredConnections.clear(); releasedConnections.clear(); - preparedStatementMap.keySet().forEach( Mockito::reset ); - preparedStatementMap.clear(); + spyContext.clear(); } /** diff --git a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java index 7a4c58236d9c..3894f6a0f56a 100644 --- a/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java +++ b/hibernate-hikaricp/src/test/java/org/hibernate/test/hikaricp/HikariCPSkipAutoCommitTest.java @@ -7,7 +7,6 @@ package org.hibernate.test.hikaricp; import java.sql.Connection; -import java.sql.SQLException; import java.util.List; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -15,26 +14,21 @@ import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Configuration; -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.SkipForDialect; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.dialect.SybaseDialect; import org.hibernate.orm.test.util.PreparedStatementSpyConnectionProvider; + import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; /** * @author Vlad Mihalcea */ -@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) @SkipForDialect(value = SybaseDialect.class, comment = "The jTDS driver doesn't implement Connection#isValid so this fails") public class HikariCPSkipAutoCommitTest extends BaseCoreFunctionalTestCase { @@ -91,12 +85,15 @@ private void verifyConnections() { List connections = connectionProvider.getReleasedConnections(); assertEquals( 1, connections.size() ); - Connection connection = connections.get( 0 ); try { - verify(connection, never()).setAutoCommit( false ); + List setAutoCommitCalls = connectionProvider.spyContext.getCalls( + Connection.class.getMethod( "setAutoCommit", boolean.class ), + connections.get( 0 ) + ); + assertTrue( "setAutoCommit should never be called", setAutoCommitCalls.isEmpty() ); } - catch (SQLException e) { - fail(e.getMessage()); + catch (NoSuchMethodException e) { + throw new RuntimeException( e ); } } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/JdbcSpies.java b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/JdbcSpies.java new file mode 100644 index 000000000000..6cf5eb03d1d5 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/JdbcSpies.java @@ -0,0 +1,578 @@ +/* + * 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.testing.jdbc; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Christian Beikov + */ +@SuppressWarnings({"unused"}) +public class JdbcSpies { + + public interface Callback { + void onCall(Object spy, Method method, Object[] args, Object result); + } + public static class SpyContext { + private final Map>> calls = new HashMap<>(); + private final List callbacks = new ArrayList<>(); + + private Object call(Object spy, Object realObject, Method method, Object[] args) throws Throwable { + return onCall( spy, method, args, callOnly( realObject, method, args ) ); + } + + private Object callOnly(Object realObject, Method method, Object[] args) throws Throwable { + try { + return method.invoke( realObject, args ); + } + catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + + private T onCall(Object spy, Method method, Object[] args, T result) { + calls.computeIfAbsent( method, m -> new IdentityHashMap<>() ) + .computeIfAbsent( spy, s -> new ArrayList<>() ) + .add( args ); + for ( Callback callback : callbacks ) { + callback.onCall( spy, method, args, result ); + } + return result; + } + + public SpyContext registerCallback(Callback callback) { + callbacks.add( callback ); + return this; + } + + public List getCalls(Method method, Object spy) { + return calls.getOrDefault( method, Collections.emptyMap() ).getOrDefault( spy, Collections.emptyList() ); + } + + public void clear() { + calls.clear(); + } + + public T getSpiedInstance(T spy) { + if ( Proxy.isProxyClass( spy.getClass() ) ) { + final InvocationHandler invocationHandler = Proxy.getInvocationHandler( spy ); + if ( invocationHandler instanceof Spy ) { + //noinspection unchecked + return (T) ( (Spy) invocationHandler ).getSpiedInstance(); + } + } + throw new IllegalArgumentException( "Passed object is not a spy: " + spy ); + } + } + + public static Connection spy(Connection connection, SpyContext context) { + return (Connection) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[]{ Connection.class }, + new ConnectionHandler( connection, context ) + ); + } + + private interface Spy { + Object getSpiedInstance(); + } + + private static class ConnectionHandler implements InvocationHandler, Spy { + private final Connection connection; + private final SpyContext context; + private DatabaseMetaData databaseMetaDataProxy; + + public ConnectionHandler(Connection connection, SpyContext context) { + this.connection = connection; + this.context = context; + } + + @Override + public Object getSpiedInstance() { + return connection; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch ( method.getName() ) { + case "getMetaData": + return context.onCall( proxy, method, args, getDatabaseMetaDataProxy( (Connection) proxy ) ); + case "createStatement": + return context.onCall( + proxy, + method, + args, + createStatementProxy( + (Statement) context.callOnly( connection, method, args ), + (Connection) proxy + ) + ); + case "prepareStatement": + return context.onCall( + proxy, + method, + args, + prepareStatementProxy( + (PreparedStatement) context.callOnly( connection, method, args ), + (Connection) proxy + ) + ); + case "prepareCall": + return context.onCall( + proxy, + method, + args, + prepareCallProxy( + (CallableStatement) context.callOnly( connection, method, args ), + (Connection) proxy + ) + ); + case "toString": + return context.onCall( proxy, method, args, "Connection proxy [@" + hashCode() + "]" ); + case "hashCode": + return context.onCall( proxy, method, args, hashCode() ); + case "equals": + return context.onCall( proxy, method, args, proxy == args[0] ); + case "setSavepoint": + return savepointProxy( (Savepoint) context.call( proxy, connection, method, args ) ); + case "releaseSavepoint": + if ( Proxy.isProxyClass( args[0].getClass() ) ) { + args[0] = ( (SavepointHandler) Proxy.getInvocationHandler( args[0] ) ).savepoint; + } + return context.call( proxy, connection, method, args ); + case "rollback": + if ( args != null && args.length != 0 && Proxy.isProxyClass( args[0].getClass() ) ) { + args[0] = ( (SavepointHandler) Proxy.getInvocationHandler( args[0] ) ).savepoint; + } + return context.call( proxy, connection, method, args ); + default: + return context.call( proxy, connection, method, args ); + } + } + + private DatabaseMetaData getDatabaseMetaDataProxy(Connection connectionProxy) throws Throwable { + if ( databaseMetaDataProxy == null ) { + // we need to make it + final DatabaseMetaDataHandler metadataHandler = new DatabaseMetaDataHandler( connection.getMetaData(), connectionProxy, context ); + databaseMetaDataProxy = (DatabaseMetaData) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {DatabaseMetaData.class}, + metadataHandler + ); + } + return databaseMetaDataProxy; + } + + private Statement createStatementProxy(Statement statement, Connection connectionProxy) { + return (Statement) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {Statement.class}, + new StatementHandler( statement, context, connectionProxy ) + ); + } + + private PreparedStatement prepareStatementProxy(PreparedStatement statement, Connection connectionProxy) { + return (PreparedStatement) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {PreparedStatement.class}, + new PreparedStatementHandler( statement, context, connectionProxy ) + ); + } + + private CallableStatement prepareCallProxy(CallableStatement statement, Connection connectionProxy) { + return (CallableStatement) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {CallableStatement.class}, + new CallableStatementHandler( statement, context, connectionProxy ) + ); + } + + private Savepoint savepointProxy(Savepoint savepoint) { + return (Savepoint) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {Savepoint.class}, + new SavepointHandler( savepoint, context ) + ); + } + } + + private static class StatementHandler implements InvocationHandler, Spy { + protected final Statement statement; + protected final SpyContext context; + protected final Connection connectionProxy; + + public StatementHandler(Statement statement, SpyContext context, Connection connectionProxy) { + this.statement = statement; + this.context = context; + this.connectionProxy = connectionProxy; + } + + @Override + public Object getSpiedInstance() { + return statement; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch ( method.getName() ) { + case "getConnection": + return context.onCall( proxy, method, args, connectionProxy ); + case "toString": + return context.onCall( proxy, method, args, "Statement proxy [@" + hashCode() + "]" ); + case "hashCode": + return context.onCall( proxy, method, args, hashCode() ); + case "equals": + return context.onCall( proxy, method, args, proxy == args[0] ); + case "executeQuery": + return context.onCall( proxy, method, args, getResultSetProxy( statement.executeQuery( (String) args[0] ), (Statement) proxy ) ); + case "getResultSet": + return context.onCall( proxy, method, args, getResultSetProxy( statement.getResultSet(), (Statement) proxy ) ); + case "getGeneratedKeys": + return context.onCall( proxy, method, args, getResultSetProxy( statement.getGeneratedKeys(), (Statement) proxy ) ); + default: + return context.call( proxy, statement, method, args ); + } + } + + protected ResultSet getResultSetProxy(ResultSet resultSet, Statement statementProxy) throws Throwable { + return (ResultSet) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {ResultSet.class}, + new ResultSetHandler( resultSet, context, statementProxy ) + ); + } + } + + private static class PreparedStatementHandler extends StatementHandler { + private ResultSetMetaData resultSetMetaDataProxy; + private ParameterMetaData parameterMetaDataProxy; + + public PreparedStatementHandler(PreparedStatement statement, SpyContext context, Connection connectionProxy) { + super( statement, context, connectionProxy ); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch ( method.getName() ) { + case "toString": + return context.onCall( proxy, method, args, "PreparedStatement proxy [@" + hashCode() + "]" ); + case "executeQuery": + return context.onCall( + proxy, + method, + args, + getResultSetProxy( + (ResultSet) context.callOnly( statement, method, args ), + (PreparedStatement) proxy + ) + ); + case "getMetaData": + return context.onCall( + proxy, + method, + args, + getResultSetMetaDataProxy( ( (PreparedStatement) statement ).getMetaData() ) + ); + case "getParameterMetaData": + return context.onCall( + proxy, + method, + args, + getParameterMetaDataProxy( ( (PreparedStatement) statement ).getParameterMetaData() ) + ); + default: + return super.invoke( proxy, method, args ); + } + } + + private ResultSetMetaData getResultSetMetaDataProxy(ResultSetMetaData metaData) throws Throwable { + if ( resultSetMetaDataProxy == null ) { + // we need to make it + resultSetMetaDataProxy = (ResultSetMetaData) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {ResultSetMetaData.class}, + new ResultSetMetaDataHandler( metaData, context ) + ); + } + return resultSetMetaDataProxy; + } + + private ParameterMetaData getParameterMetaDataProxy(ParameterMetaData metaData) throws Throwable { + if ( parameterMetaDataProxy == null ) { + // we need to make it + parameterMetaDataProxy = (ParameterMetaData) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {ParameterMetaData.class}, + new ParameterMetaDataHandler( metaData, context ) + ); + } + return parameterMetaDataProxy; + } + } + + private static class CallableStatementHandler extends PreparedStatementHandler { + + public CallableStatementHandler(CallableStatement statement, SpyContext context, Connection connectionProxy) { + super( statement, context, connectionProxy ); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ( "toString".equals( method.getName() ) ) { + return context.onCall( proxy, method, args, "CallableStatement proxy [@" + hashCode() + "]" ); + } + else { + return super.invoke( proxy, method, args ); + } + } + } + + private static class DatabaseMetaDataHandler implements InvocationHandler, Spy { + private final DatabaseMetaData databaseMetaData; + private final Connection connectionProxy; + private final SpyContext context; + + public DatabaseMetaDataHandler( + DatabaseMetaData databaseMetaData, + Connection connectionProxy, + SpyContext context) { + this.databaseMetaData = databaseMetaData; + this.connectionProxy = connectionProxy; + this.context = context; + } + + @Override + public Object getSpiedInstance() { + return databaseMetaData; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch ( method.getName() ) { + case "getConnection": + return context.onCall( proxy, method, args, connectionProxy ); + case "toString": + return context.onCall( proxy, method, args, "DatabaseMetaData proxy [@" + hashCode() + "]" ); + case "hashCode": + return context.onCall( proxy, method, args, hashCode() ); + case "equals": + return context.onCall( proxy, method, args, proxy == args[0] ); + case "getProcedures": + case "getProcedureColumns": + case "getTables": + case "getSchemas": + case "getCatalogs": + case "getTableTypes": + case "getColumns": + case "getColumnPrivileges": + case "getTablePrivileges": + case "getBestRowIdentifier": + case "getVersionColumns": + case "getPrimaryKeys": + case "getImportedKeys": + case "getExportedKeys": + case "getCrossReference": + case "getTypeInfo": + case "getIndexInfo": + case "getUDTs": + case "getSuperTypes": + case "getSuperTables": + case "getAttributes": + case "getClientInfoProperties": + case "getFunctions": + case "getFunctionColumns": + case "getPseudoColumns": + final ResultSet resultSet = (ResultSet) context.callOnly( databaseMetaData, method, args ); + return context.onCall( proxy, method, args, getResultSetProxy( resultSet, getStatementProxy( resultSet.getStatement() ) ) ); + default: + return context.call( proxy, databaseMetaData, method, args ); + } + } + + protected ResultSet getResultSetProxy(ResultSet resultSet, Statement statementProxy) throws Throwable { + return (ResultSet) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {ResultSet.class}, + new ResultSetHandler( resultSet, context, statementProxy ) + ); + } + + protected Statement getStatementProxy(Statement statement) throws Throwable { + final InvocationHandler handler; + if ( statement instanceof CallableStatement ) { + handler = new CallableStatementHandler( (CallableStatement) statement, context, connectionProxy ); + } + else if ( statement instanceof PreparedStatement ) { + handler = new PreparedStatementHandler( (PreparedStatement) statement, context, connectionProxy ); + } + else { + handler = new StatementHandler( statement, context, connectionProxy ); + } + return (Statement) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {Statement.class}, + handler + ); + } + } + + private static class ParameterMetaDataHandler implements InvocationHandler, Spy { + private final ParameterMetaData parameterMetaData; + private final SpyContext context; + + public ParameterMetaDataHandler(ParameterMetaData parameterMetaData, SpyContext context) { + this.parameterMetaData = parameterMetaData; + this.context = context; + } + + @Override + public Object getSpiedInstance() { + return parameterMetaData; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch ( method.getName() ) { + case "toString": + return context.onCall( proxy, method, args, "DatabaseMetaData proxy [@" + hashCode() + "]" ); + case "hashCode": + return context.onCall( proxy, method, args, hashCode() ); + case "equals": + return context.onCall( proxy, method, args, proxy == args[0] ); + default: + return context.call( proxy, parameterMetaData, method, args ); + } + } + } + + private static class ResultSetHandler implements InvocationHandler, Spy { + private final ResultSet resultSet; + private final SpyContext context; + private final Statement statementProxy; + private ResultSetMetaData metadataProxy; + + public ResultSetHandler(ResultSet resultSet, SpyContext context, Statement statementProxy) { + this.resultSet = resultSet; + this.context = context; + this.statementProxy = statementProxy; + } + + @Override + public Object getSpiedInstance() { + return resultSet; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + final String methodName = method.getName(); + switch ( methodName ) { + case "getMetaData": + return context.onCall( proxy, method, args, getResultSetMetaDataProxy( resultSet.getMetaData() ) ); + case "getStatement": + return context.onCall( proxy, method, args, statementProxy ); + case "toString": + return context.onCall( proxy, method, args, "ResultSet proxy [@" + hashCode() + "]" ); + case "hashCode": + return context.onCall( proxy, method, args, hashCode() ); + case "equals": + return context.onCall( proxy, method, args, proxy == args[0] ); + default: + return context.call( proxy, resultSet, method, args ); + } + } + + private ResultSetMetaData getResultSetMetaDataProxy(ResultSetMetaData metaData) throws Throwable { + if ( metadataProxy == null ) { + // we need to make it + final ResultSetMetaDataHandler metadataHandler = new ResultSetMetaDataHandler( metaData, context ); + metadataProxy = (ResultSetMetaData) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] {ResultSetMetaData.class}, + metadataHandler + ); + } + return metadataProxy; + } + } + + private static class ResultSetMetaDataHandler implements InvocationHandler, Spy { + private final ResultSetMetaData resultSetMetaData; + private final SpyContext context; + + public ResultSetMetaDataHandler(ResultSetMetaData resultSetMetaData, SpyContext context) { + this.resultSetMetaData = resultSetMetaData; + this.context = context; + } + + @Override + public Object getSpiedInstance() { + return resultSetMetaData; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch ( method.getName() ) { + case "toString": + return context.onCall( proxy, method, args, "ResultSetMetaData proxy [@" + hashCode() + "]" ); + case "hashCode": + return context.onCall( proxy, method, args, hashCode() ); + case "equals": + return context.onCall( proxy, method, args, proxy == args[0] ); + default: + return context.call( proxy, resultSetMetaData, method, args ); + } + } + } + + private static class SavepointHandler implements InvocationHandler, Spy { + private final Savepoint savepoint; + private final SpyContext context; + + public SavepointHandler(Savepoint savepoint, SpyContext context) { + this.savepoint = savepoint; + this.context = context; + } + + @Override + public Object getSpiedInstance() { + return savepoint; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + switch ( method.getName() ) { + case "toString": + return context.onCall( proxy, method, args, "Savepoint proxy [@" + hashCode() + "]" ); + case "hashCode": + return context.onCall( proxy, method, args, hashCode() ); + case "equals": + return context.onCall( proxy, method, args, proxy == args[0] ); + default: + return context.call( proxy, savepoint, method, args ); + } + } + } + +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProvider.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProvider.java index cce32918eca3..d611e15914c4 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProvider.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProvider.java @@ -14,18 +14,12 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Queue; -import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Collectors; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.testing.jdbc.ConnectionProviderDelegate; - -import org.mockito.ArgumentMatchers; -import org.mockito.MockSettings; -import org.mockito.Mockito; -import org.mockito.internal.util.MockUtil; +import org.hibernate.testing.jdbc.JdbcSpies; /** * This {@link ConnectionProvider} extends any other ConnectionProvider that would be used by default taken the current configuration properties, and it @@ -36,48 +30,38 @@ */ public class PreparedStatementSpyConnectionProvider extends ConnectionProviderDelegate { - private static final MockSettings MOCK_SETTINGS = Mockito.withSettings() - .stubOnly() //important optimisation: uses far less memory, at tradeoff of mocked methods no longer being verifiable but we often don't need that. - .defaultAnswer( org.mockito.Answers.CALLS_REAL_METHODS ); - private static final MockSettings VERIFIABLE_MOCK_SETTINGS = Mockito.withSettings() - .defaultAnswer( org.mockito.Answers.CALLS_REAL_METHODS ); - // We must keep around the mocked connections, otherwise they are garbage collected and trigger finalizers - // Since we use CALLS_REAL_METHODS this might close underlying IO resources which makes other objects unusable - private static final Queue MOCKS = new LinkedBlockingQueue<>(); - private final Map preparedStatementMap = new LinkedHashMap<>(); - private final List executeStatements = new ArrayList<>( 4 ); private final List executeUpdateStatements = new ArrayList<>( 4 ); + public final JdbcSpies.SpyContext spyContext = new JdbcSpies.SpyContext() + .registerCallback( + (spy, method, args, result) -> { + if ( method.getDeclaringClass() == Connection.class + && method.getName().equals( "prepareStatement" ) ) { + preparedStatementMap.put( (PreparedStatement) result, (String) args[0] ); + } + else if ( method.getDeclaringClass() == Statement.class + && method.getName().equals( "execute" ) ) { + executeStatements.add( (String) args[0] ); + } + else if ( method.getDeclaringClass() == Statement.class + && method.getName().equals( "executeUpdate" ) ) { + executeUpdateStatements.add( (String) args[0] ); + } + } + ); + + private final List acquiredConnections = new ArrayList<>( 4 ); private final List releasedConnections = new ArrayList<>( 4 ); - private final MockSettings settingsForStatements; - private final MockSettings settingsForConnections; - /** - * @deprecated best use the {@link #PreparedStatementSpyConnectionProvider(boolean,boolean)} method to be explicit about the limitations. - */ - @Deprecated public PreparedStatementSpyConnectionProvider() { - this( false, false, false ); - } - - /** - * Careful: the default is to use mocks which do not allow to verify invocations, as otherwise the - * memory usage of the testsuite is extremely high. - * When you really need to verify invocations, set the relevant constructor parameter to true. - */ - public PreparedStatementSpyConnectionProvider( - boolean allowMockVerificationOnStatements, - boolean allowMockVerificationOnConnections) { - this( allowMockVerificationOnStatements, allowMockVerificationOnConnections, false ); + this( false ); } - public PreparedStatementSpyConnectionProvider(boolean allowMockVerificationOnStatements, boolean allowMockVerificationOnConnections, boolean forceSupportsAggressiveRelease) { + public PreparedStatementSpyConnectionProvider(boolean forceSupportsAggressiveRelease) { super(forceSupportsAggressiveRelease); - this.settingsForStatements = allowMockVerificationOnStatements ? VERIFIABLE_MOCK_SETTINGS : MOCK_SETTINGS; - this.settingsForConnections = allowMockVerificationOnConnections ? VERIFIABLE_MOCK_SETTINGS : MOCK_SETTINGS; } protected Connection actualConnection() throws SQLException { @@ -87,7 +71,6 @@ protected Connection actualConnection() throws SQLException { @Override public Connection getConnection() throws SQLException { Connection connection = instrumentConnection( actualConnection() ); - MOCKS.add( connection ); acquiredConnections.add( connection ); return connection; } @@ -96,7 +79,7 @@ public Connection getConnection() throws SQLException { public void closeConnection(Connection conn) throws SQLException { acquiredConnections.remove( conn ); releasedConnections.add( conn ); - super.closeConnection( (Connection) MockUtil.getMockSettings( conn ).getSpiedInstance() ); + super.closeConnection( spyContext.getSpiedInstance( conn ) ); } @Override @@ -106,51 +89,7 @@ public void stop() { } private Connection instrumentConnection(Connection connection) { - if ( MockUtil.isMock( connection ) ) { - return connection; - } - Connection connectionSpy = spy( connection, settingsForConnections ); - try { - Mockito.doAnswer( invocation -> { - PreparedStatement statement = (PreparedStatement) invocation.callRealMethod(); - PreparedStatement statementSpy = spy( statement, settingsForStatements ); - String sql = (String) invocation.getArguments()[0]; - preparedStatementMap.put( statementSpy, sql ); - return statementSpy; - } ).when( connectionSpy ).prepareStatement( ArgumentMatchers.anyString() ); - - Mockito.doAnswer( invocation -> { - PreparedStatement statement = (PreparedStatement) invocation.callRealMethod(); - PreparedStatement statementSpy = spy( statement, settingsForStatements ); - String sql = (String) invocation.getArguments()[0]; - preparedStatementMap.put( statementSpy, sql ); - return statementSpy; - } ).when( connectionSpy ).prepareCall( ArgumentMatchers.anyString() ); - - Mockito.doAnswer( invocation -> { - Statement statement = (Statement) invocation.callRealMethod(); - Statement statementSpy = spy( statement, settingsForStatements ); - Mockito.doAnswer( statementInvocation -> { - String sql = (String) statementInvocation.getArguments()[0]; - executeStatements.add( sql ); - return statementInvocation.callRealMethod(); - } ).when( statementSpy ).execute( ArgumentMatchers.anyString() ); - Mockito.doAnswer( statementInvocation -> { - String sql = (String) statementInvocation.getArguments()[0]; - executeUpdateStatements.add( sql ); - return statementInvocation.callRealMethod(); - } ).when( statementSpy ).executeUpdate( ArgumentMatchers.anyString() ); - return statementSpy; - } ).when( connectionSpy ).createStatement(); - } - catch ( SQLException e ) { - throw new IllegalArgumentException( e ); - } - return connectionSpy; - } - - private static T spy(T subject, MockSettings mockSettings) { - return Mockito.mock( (Class) subject.getClass(), mockSettings.spiedInstance( subject ) ); + return JdbcSpies.spy( connection, spyContext ); } /** @@ -159,7 +98,7 @@ private static T spy(T subject, MockSettings mockSettings) { public void clear() { acquiredConnections.clear(); releasedConnections.clear(); - preparedStatementMap.keySet().forEach( Mockito::reset ); + spyContext.clear(); preparedStatementMap.clear(); executeStatements.clear(); executeUpdateStatements.clear(); diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProviderSettingProvider.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProviderSettingProvider.java index a4722cc93ab3..845bcd2021eb 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProviderSettingProvider.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProviderSettingProvider.java @@ -14,6 +14,6 @@ public class PreparedStatementSpyConnectionProviderSettingProvider implements SettingProvider.Provider { @Override public PreparedStatementSpyConnectionProvider getSetting() { - return new PreparedStatementSpyConnectionProvider( false, false ); + return new PreparedStatementSpyConnectionProvider(); } } From 3d9b1fb4cf744c7bbd7c3c3967089a5c149352f1 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 9 Feb 2023 10:56:35 +0100 Subject: [PATCH 0080/1497] HHH-16119 Add test for issue --- ...ynamicInstantiationAndTupleResultTest.java | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/query/NativeQueryDynamicInstantiationAndTupleResultTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/NativeQueryDynamicInstantiationAndTupleResultTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/NativeQueryDynamicInstantiationAndTupleResultTest.java new file mode 100644 index 000000000000..cad030b804dc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/NativeQueryDynamicInstantiationAndTupleResultTest.java @@ -0,0 +1,173 @@ +package org.hibernate.orm.test.query; + +import java.util.List; + +import org.hibernate.query.spi.QueryImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.ColumnResult; +import jakarta.persistence.ConstructorResult; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.NamedNativeQueries; +import jakarta.persistence.NamedNativeQuery; +import jakarta.persistence.SqlResultSetMapping; +import jakarta.persistence.Table; +import jakarta.persistence.Tuple; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DomainModel( + annotatedClasses = { + NativeQueryDynamicInstantiationAndTupleResultTest.Demo.class + } +) +@SessionFactory +@TestForIssue(jiraKey = "HHH-16199") +public class NativeQueryDynamicInstantiationAndTupleResultTest { + + public static final String DEMO_NAME = "it is a demo demo"; + + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Demo entity = new Demo( DEMO_NAME ); + session.persist( entity ); + } + ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "delete from Demo" ).executeUpdate(); + } + ); + } + + @Test + public void testQuery(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + QueryImplementor query = session.createNamedQuery( "Demo.findAllCustom" ); + List resultList = query.getResultList(); + assertThat( resultList.size() ).isEqualTo( 1 ); + + DemoPojo demoPojo = resultList.get( 0 ); + assertThat( demoPojo.getId() ).isNotNull(); + assertThat( demoPojo.getName() ).isEqualTo( DEMO_NAME ); + } + ); + } + + @Test + public void testQuery2(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + QueryImplementor query = session.createNamedQuery( "Demo.findAllCustom", DemoPojo.class ); + List resultList = query.getResultList(); + assertThat( resultList.size() ).isEqualTo( 1 ); + + DemoPojo demoPojo = resultList.get( 0 ); + assertThat( demoPojo.getId() ).isNotNull(); + assertThat( demoPojo.getName() ).isEqualTo( DEMO_NAME ); + } + ); + } + + @Test + public void testQuery3(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + QueryImplementor query = session.createNamedQuery( "Demo.findAllCustom", Tuple.class ); + List resultList = query.getResultList(); + assertThat( resultList.size() ).isEqualTo( 1 ); + + Tuple tuple = resultList.get( 0 ); + DemoPojo demoPojo = tuple.get( 0, DemoPojo.class ); + assertThat( demoPojo.getId() ).isNotNull(); + assertThat( demoPojo.getName() ).isEqualTo( DEMO_NAME ); + } + ); + } + + @Entity(name = "Demo") + @Table(name = "DEMO_TABLE") + @NamedNativeQueries({ + @NamedNativeQuery( + name = "Demo.findAllCustom", + query = "select * from DEMO_TABLE", + resultSetMapping = "demo" + ) + }) + @SqlResultSetMapping( + name = "demo", + classes = @ConstructorResult( + targetClass = DemoPojo.class, + columns = { + @ColumnResult(name = "id", type = Long.class), + @ColumnResult(name = "name", type = String.class) + } + ) + ) + public static class Demo { + @Id + @GeneratedValue + private Long id; + + private String name; + + public Demo() { + } + + public Demo(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + } + + public static class DemoPojo { + private Long id; + + private String name; + + public DemoPojo(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} From df4135c7f0ba329886f6e75f94a75029efe70c92 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Thu, 9 Feb 2023 12:13:40 +0100 Subject: [PATCH 0081/1497] HHH-16119 Named native queries do not work with jakarta.persistence.Tuple result class --- .../jpa/spi/NativeQueryTupleTransformer.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryTupleTransformer.java b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryTupleTransformer.java index 440082fc42d3..7634dd5a428b 100644 --- a/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryTupleTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/jpa/spi/NativeQueryTupleTransformer.java @@ -60,6 +60,8 @@ private static class NativeTupleImpl implements Tuple { private final Object[] tuple; + private final int size; + private final Map aliasToValue = new LinkedHashMap<>(); private final Map aliasReferences = new LinkedHashMap<>(); @@ -75,9 +77,13 @@ public NativeTupleImpl(Object[] tuple, String[] aliases) { } this.tuple = tuple; for ( int i = 0; i < tuple.length; i++ ) { - aliasToValue.put( aliases[i], tuple[i] ); - aliasReferences.put( aliases[i].toLowerCase(), aliases[i] ); + final String alias = aliases[i]; + if ( alias != null ) { + aliasToValue.put( alias, tuple[i] ); + aliasReferences.put( alias.toLowerCase(), alias ); + } } + size = tuple.length; } @Override @@ -108,7 +114,7 @@ public Object get(int i) { if ( i < 0 ) { throw new IllegalArgumentException( "requested tuple index must be greater than zero" ); } - if ( i >= aliasToValue.size() ) { + if ( i >= size ) { throw new IllegalArgumentException( "requested tuple index exceeds actual tuple size" ); } return tuple[i]; @@ -122,7 +128,7 @@ public Object[] toArray() { @Override public List> getElements() { - List> elements = new ArrayList<>( aliasToValue.size() ); + List> elements = new ArrayList<>( size ); for ( Map.Entry entry : aliasToValue.entrySet() ) { elements.add( new NativeTupleElementImpl<>( getValueClass( entry.getValue() ), entry.getKey() ) ); From 97d505775157e3af925dd240a73fe5b3ea26744d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Tue, 7 Feb 2023 16:32:01 +0100 Subject: [PATCH 0082/1497] HHH-16150 Fix schema not being dropped on bootstrap failure with the "create-drop" strategy --- .../internal/SessionFactoryImpl.java | 1 - .../jakarta/JakartaSchemaToolingTests.java | 37 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index 35f6135bf958..2c0196725c46 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -457,7 +457,6 @@ private void disintegrate(IntegratorObserver integratorObserver) { for ( Integrator integrator : serviceRegistry.getService( IntegratorService.class ).getIntegrators() ) { integrator.disintegrate( this, serviceRegistry ); integratorObserver.integrators.remove( integrator ); - serviceRegistry.close(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/jakarta/JakartaSchemaToolingTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/jakarta/JakartaSchemaToolingTests.java index 9260cfb382b6..3204f5213eab 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/jakarta/JakartaSchemaToolingTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/jakarta/JakartaSchemaToolingTests.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Properties; +import org.hibernate.SessionFactoryObserver; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -26,6 +27,8 @@ import org.hibernate.testing.transaction.TransactionUtil2; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hibernate.cfg.AvailableSettings.HBM2DDL_DATABASE_ACTION; import static org.hibernate.cfg.AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION; import static org.hibernate.cfg.AvailableSettings.JAKARTA_JDBC_DRIVER; @@ -37,6 +40,8 @@ import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_URL; import static org.hibernate.cfg.AvailableSettings.JPA_JDBC_USER; +import org.assertj.core.api.Assertions; + /** * @author Steve Ebersole */ @@ -102,6 +107,38 @@ public void testPrecedence() { } } + @Test + public void testCreateDropWithFailureInBetween() { + // Make sure that when using the "create-drop" database action, when a failure occur after schema is created, + // the schema is correctly dropped. + assertThatThrownBy( () -> buildSessionFactory( + JAKARTA_HBM2DDL_DATABASE_ACTION, Action.CREATE_DROP, + JAKARTA_JDBC_DRIVER, Environment.getProperties().get( AvailableSettings.DRIVER ), + JAKARTA_JDBC_URL, Environment.getProperties().get( AvailableSettings.URL ), + JAKARTA_JDBC_USER, Environment.getProperties().get( AvailableSettings.USER ), + JAKARTA_JDBC_PASSWORD, Environment.getProperties().get( AvailableSettings.PASS ), + // Simulates a failure from e.g. the Hibernate Search observer + AvailableSettings.SESSION_FACTORY_OBSERVER, new SessionFactoryObserver() { + @Override + public void sessionFactoryCreated(org.hibernate.SessionFactory factory) { + throw new RuntimeException( "Simulated failure" ); + } + } + ) ) + .hasRootCauseMessage( "Simulated failure" ); + + // Now check that the schema was dropped: queries should fail. + try ( SessionFactoryImplementor sessionFactory = buildSessionFactory( + JAKARTA_HBM2DDL_DATABASE_ACTION, Action.NONE, + JAKARTA_JDBC_DRIVER, Environment.getProperties().get( AvailableSettings.DRIVER ), + JAKARTA_JDBC_URL, Environment.getProperties().get( AvailableSettings.URL ), + JAKARTA_JDBC_USER, Environment.getProperties().get( AvailableSettings.USER ), + JAKARTA_JDBC_PASSWORD, Environment.getProperties().get( AvailableSettings.PASS ) + ) ) { + assertThatThrownBy( () -> tryQuery( sessionFactory ) ).isNotNull(); + } + } + private SessionFactoryImplementor buildSessionFactory(Object... settingPairs) { final Properties settings = CollectionHelper.toProperties( settingPairs ); From 1a9f545e790a9f6a4d3e840a1a135fb685ca0914 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 7 Feb 2023 17:42:55 +0100 Subject: [PATCH 0083/1497] HHH-16150 Fix a little issue with SessionFactoryImpl#disintegrate --- .../hibernate/internal/SessionFactoryImpl.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index 2c0196725c46..9620998e4186 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -371,7 +371,7 @@ public ServiceRegistry getServiceRegistry() { } catch ( Exception e ) { - disintegrate( integratorObserver ); + disintegrate( e, integratorObserver ); try { close(); @@ -453,11 +453,16 @@ private void integrate(MetadataImplementor bootMetamodel, BootstrapContext boots } } - private void disintegrate(IntegratorObserver integratorObserver) { - for ( Integrator integrator : serviceRegistry.getService( IntegratorService.class ).getIntegrators() ) { - integrator.disintegrate( this, serviceRegistry ); - integratorObserver.integrators.remove( integrator ); + private void disintegrate(Exception startupException, IntegratorObserver integratorObserver) { + for ( Integrator integrator : integratorObserver.integrators ) { + try { + integrator.disintegrate( this, serviceRegistry ); + } + catch (Throwable ex) { + startupException.addSuppressed( ex ); + } } + integratorObserver.integrators.clear(); } From 16c74bc8041dadfd56772acdd67016f7e2262022 Mon Sep 17 00:00:00 2001 From: Paul Ferraro Date: Sat, 11 Feb 2023 22:01:13 -0500 Subject: [PATCH 0084/1497] HHH-16172 Expose internal state of 2nd-level cache keys --- .../internal/BasicCacheKeyImplementation.java | 28 +++++++++++--- .../internal/CacheKeyImplementation.java | 38 ++++++++++++++++--- .../cache/internal/NaturalIdCacheKey.java | 16 ++++---- 3 files changed, 65 insertions(+), 17 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/BasicCacheKeyImplementation.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/BasicCacheKeyImplementation.java index e241ba34c609..7655ac1e0c68 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/BasicCacheKeyImplementation.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/BasicCacheKeyImplementation.java @@ -7,10 +7,8 @@ package org.hibernate.cache.internal; import java.io.Serializable; -import java.util.Objects; import org.hibernate.Internal; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.type.Type; /** @@ -44,11 +42,26 @@ public BasicCacheKeyImplementation( final Serializable disassembledKey, final Type type, final String entityOrRoleName) { - assert disassembledKey != null; + this( disassembledKey, entityOrRoleName, calculateHashCode( originalId, type ) ); + } + + /** + * Being an internal contract the arguments are not being checked. + * @param originalId + * @param disassembledKey this must be the "disassembled" form of an ID + * @param type + * @param entityOrRoleName + */ + @Internal + public BasicCacheKeyImplementation( + final Serializable id, + final String entityOrRoleName, + final int hashCode) { + assert id != null; assert entityOrRoleName != null; - this.id = disassembledKey; + this.id = id; this.entityOrRoleName = entityOrRoleName; - this.hashCode = calculateHashCode( originalId, type ); + this.hashCode = hashCode; } private static int calculateHashCode(Object disassembledKey, Type type) { @@ -59,6 +72,11 @@ public Object getId() { return id; } + @Internal + public String getEntityOrRoleName() { + return entityOrRoleName; + } + @Override public boolean equals(final Object other) { if ( other == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java index 2822837c6dbd..1c0569d37c30 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/CacheKeyImplementation.java @@ -10,7 +10,6 @@ import java.util.Objects; import org.hibernate.Internal; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.type.Type; /** @@ -31,7 +30,7 @@ public final class CacheKeyImplementation implements Serializable { private final String tenantId; private final int hashCode; - //because of object alignmnet, we had "free space" in this key: + //because of object alignment, we had "free space" in this key: //this field isn't strictly necessary but convenient: watch for //class layout changes. private final boolean requiresDeepEquals; @@ -54,12 +53,31 @@ public CacheKeyImplementation( final Type type, final String entityOrRoleName, final String tenantId) { + this( disassembledKey, entityOrRoleName, tenantId, calculateHashCode( id, type, tenantId ) ); + } + + /** + * Construct a new key for a collection or entity instance. + * Note that an entity name should always be the root entity + * name, not a subclass entity name. + * + * @param id The identifier associated with the cached data + * @param entityOrRoleName The entity or collection-role name. + * @param tenantId The tenant identifier associated with this data. + * @param hashCode the pre-calculated hash code + */ + @Internal + public CacheKeyImplementation( + final Object id, + final String entityOrRoleName, + final String tenantId, + final int hashCode) { assert entityOrRoleName != null; - this.id = disassembledKey; + this.id = id; this.entityOrRoleName = entityOrRoleName; this.tenantId = tenantId; //might actually be null - this.hashCode = calculateHashCode( id, type, tenantId ); - this.requiresDeepEquals = disassembledKey.getClass().isArray(); + this.hashCode = hashCode; + this.requiresDeepEquals = id.getClass().isArray(); } private static int calculateHashCode(Object id, Type type, String tenantId) { @@ -72,6 +90,16 @@ public Object getId() { return id; } + @Internal + public String getEntityOrRoleName() { + return entityOrRoleName; + } + + @Internal + public String getTenantId() { + return tenantId; + } + @Override public boolean equals(Object other) { if ( other == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/NaturalIdCacheKey.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/NaturalIdCacheKey.java index a8e09604a7a1..89abfd58388a 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/NaturalIdCacheKey.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/NaturalIdCacheKey.java @@ -11,9 +11,9 @@ import java.io.Serializable; import java.util.Objects; +import org.hibernate.Internal; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.ValueHolder; -import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.persister.entity.EntityPersister; /** @@ -38,13 +38,15 @@ public NaturalIdCacheKey(Object naturalIdValues, EntityPersister persister, Shar } public NaturalIdCacheKey(Object naturalIdValues, EntityPersister persister, String entityName, SharedSessionContractImplementor session) { - this.entityName = entityName; - this.tenantId = session.getTenantIdentifier(); - - final NaturalIdMapping naturalIdMapping = persister.getNaturalIdMapping(); + this( persister.getNaturalIdMapping().disassemble( naturalIdValues, session ), entityName, session.getTenantIdentifier(), persister.getNaturalIdMapping().calculateHashCode( naturalIdValues ) ); + } - this.naturalIdValues = naturalIdMapping.disassemble( naturalIdValues, session ); - this.hashCode = naturalIdMapping.calculateHashCode( naturalIdValues ); + @Internal + public NaturalIdCacheKey(Object naturalIdValues, String entityName, String tenantId, int hashCode) { + this.naturalIdValues = naturalIdValues; + this.entityName = entityName; + this.tenantId = tenantId; + this.hashCode = hashCode; initTransients(); } From 4dfac9ce2d8abc045e78abd3cbcd783120f91a60 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Tue, 14 Feb 2023 10:59:26 +0100 Subject: [PATCH 0085/1497] HHH-16080 Add test for issue --- .../onetoone/OneToOneIsNullQueryTest.java | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetoone/OneToOneIsNullQueryTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetoone/OneToOneIsNullQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetoone/OneToOneIsNullQueryTest.java new file mode 100644 index 000000000000..00edf775ead5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/onetoone/OneToOneIsNullQueryTest.java @@ -0,0 +1,113 @@ +/* + * 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.orm.test.mapping.onetoone; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.TypedQuery; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = { + OneToOneIsNullQueryTest.Thing.class, + OneToOneIsNullQueryTest.ThingStats.class +}) +@JiraKey("HHH-16080") +public class OneToOneIsNullQueryTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Thing thing1 = new Thing( 1L ); + final ThingStats stats = new ThingStats( thing1.getPk(), 10 ); + thing1.setThingStats( stats ); + final Thing thing2 = new Thing( 2L ); + session.persist( thing1 ); + session.persist( thing2 ); + session.persist( stats ); + } ); + } + + @Test + public void testIsNullQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> { + String ql = "select thing from Thing thing" + + " left join thing.thingStats thingStats " + + " where thingStats is null or thingStats.countRejected = 0"; + TypedQuery q = session.createQuery( ql, Thing.class ); + assertThat( q.getSingleResult().getPk() ).isEqualTo( 2L ); + } ); + } + + @Entity(name = "Thing") + public static class Thing { + @Id + @Column(name = "thing_pk") + private Long pk; + + @OneToOne + @JoinColumn(name = "thing_pk") + private ThingStats thingStats; + + public Thing() { + } + + public Thing(Long pk) { + this.pk = pk; + } + + public Long getPk() { + return pk; + } + + public ThingStats getThingStats() { + return thingStats; + } + + public void setThingStats(ThingStats thingStats) { + this.thingStats = thingStats; + } + } + + @Entity(name = "ThingStats") + public static class ThingStats { + @Id + @Column(name = "thing_fk", nullable = false) + private Long thingPk; + + private Integer countRejected; + + public ThingStats() { + } + + public ThingStats(Long thingPk, Integer countRejected) { + this.thingPk = thingPk; + this.countRejected = countRejected; + } + + public Long getThingPk() { + return thingPk; + } + + public Integer getCountRejected() { + return countRejected; + } + } +} From b2bbc38d2d3f2cf871a0857721f71bd1674b2be1 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Tue, 14 Feb 2023 14:11:56 +0100 Subject: [PATCH 0086/1497] HHH-16183 Change scopes of private methods in InsertCoordinator --- .../persister/entity/mutation/InsertCoordinator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java index 2d6f559fd615..464af8a4c9f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java @@ -215,7 +215,7 @@ protected void decomposeForInsert( } ); } - private static void breakDownJdbcValue( + protected void breakDownJdbcValue( Object id, SharedSessionContractImplementor session, JdbcValueBindings jdbcValueBindings, @@ -235,7 +235,7 @@ private static void breakDownJdbcValue( ); } - private void decomposeAttribute( + protected void decomposeAttribute( Object value, SharedSessionContractImplementor session, JdbcValueBindings jdbcValueBindings, From 6f16e4d9476477ded1cc01ee328173855d86a9b2 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Tue, 14 Feb 2023 17:49:54 +0100 Subject: [PATCH 0087/1497] HHH-16082 - Correction to the epoch extraction for HANA Signed-off-by: Jan Schatteman --- .../main/java/org/hibernate/dialect/AbstractHANADialect.java | 2 ++ 1 file changed, 2 insertions(+) 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 66636f85fa71..eebb2dada735 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/AbstractHANADialect.java @@ -472,6 +472,8 @@ public String extractPattern(TemporalUnit unit) { return "dayofyear(?2)"; case QUARTER: return "((month(?2)+2)/3)"; + case EPOCH: + return "seconds_between('1970-01-01', ?2)"; default: //I think week() returns the ISO week number return "?1(?2)"; From 30ad35643c2c9fbc6ba2d97b4f92cee84b0c0569 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Fri, 10 Feb 2023 21:00:47 +0100 Subject: [PATCH 0088/1497] HHH-16169 - Fix potential NullPointerException in CollectionEntry Signed-off-by: Jan Schatteman --- .../src/main/java/org/hibernate/engine/spi/CollectionEntry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java index 30c81010a241..751385bd1a4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java @@ -375,7 +375,7 @@ public void setRole(String role) { @Override public String toString() { String result = "CollectionEntry" + - MessageHelper.collectionInfoString( loadedPersister.getRole(), loadedKey ); + MessageHelper.collectionInfoString( role, loadedKey ); if ( currentPersister != null ) { result += "->" + MessageHelper.collectionInfoString( currentPersister.getRole(), currentKey ); From 56bb7581a94051167a28a34cdea73c4ced0a182a Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 15 Feb 2023 08:18:12 -0600 Subject: [PATCH 0089/1497] HHH-16182 - JPA derived query methods failing when boolean mapping using YesNoConverter --- .../mapping/basic/BooleanMappingTests.java | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BooleanMappingTests.java b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BooleanMappingTests.java index 620508fc509f..eb817a76fa4a 100644 --- a/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BooleanMappingTests.java +++ b/documentation/src/test/java/org/hibernate/userguide/mapping/basic/BooleanMappingTests.java @@ -7,11 +7,6 @@ package org.hibernate.userguide.mapping.basic; import java.sql.Types; -import jakarta.persistence.Basic; -import jakarta.persistence.Convert; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; @@ -20,10 +15,17 @@ import org.hibernate.type.internal.ConvertedBasicTypeImpl; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.Test; +import jakarta.persistence.Basic; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; @@ -93,8 +95,24 @@ public void verifyMappings(SessionFactoryScope scope) { equalTo( Types.INTEGER ) ); } + } - + @Test + @Jira( "https://hibernate.atlassian.net/browse/HHH-16182" ) + public void testQueryLiteralUsage(SessionFactoryScope scope) { + scope.inTransaction( (session) -> { + session.createSelectionQuery( "from EntityOfBooleans where convertedYesNo = true" ).list(); + session.createSelectionQuery( "from EntityOfBooleans where convertedTrueFalse = true" ).list(); + session.createSelectionQuery( "from EntityOfBooleans where convertedNumeric = true" ).list(); + + session.createMutationQuery( "delete EntityOfBooleans where convertedYesNo = true" ).executeUpdate(); + session.createMutationQuery( "delete EntityOfBooleans where convertedTrueFalse = true" ).executeUpdate(); + session.createMutationQuery( "delete EntityOfBooleans where convertedNumeric = true" ).executeUpdate(); + + session.createMutationQuery( "update EntityOfBooleans set convertedYesNo = true" ).executeUpdate(); + session.createMutationQuery( "update EntityOfBooleans set convertedTrueFalse = true" ).executeUpdate(); + session.createMutationQuery( "update EntityOfBooleans set convertedNumeric = true" ).executeUpdate(); + } ); } @Entity(name = "EntityOfBooleans") From a0050b86cdc0121366774a73123c58a56a5b75f9 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Wed, 15 Feb 2023 11:56:06 -0600 Subject: [PATCH 0090/1497] Document JUnit 5 extensions --- test-case-guide.adoc | 185 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 2 deletions(-) diff --git a/test-case-guide.adoc b/test-case-guide.adoc index fef27c472fd7..8a22f93f1ba4 100644 --- a/test-case-guide.adoc +++ b/test-case-guide.adoc @@ -13,7 +13,188 @@ There are a number of tenants that make up a good test case as opposed to a poor * (V)erifiable - The test should actually reproduce the problem being reported. -== Test templates +[[junit5]] +== JUnit 5 extensions + +JUnit 5 offers better support for integration, compared to JUnit 4, via https://junit.org/junit5/docs/current/user-guide/#extensions[extensions]. Hibernate builds on those concepts in its `hibernate-testing` module allowing set up of test fixtures using annotations. The following sections describe the Hibernate extensions. + +NOTE: The extensions exist in the `org.hibernate.testing.orm.junit` package, as opposed to the older `org.hibernate.testing.junit4` package used with JUnit 4. + + +[[junit5-service-registry]] +=== ServiceRegistryExtension + +Manages a `ServiceRegistry` as part of the test lifecycle. 2 in fact, depending on the annotation(s) used. + +`@BootstrapServiceRegistry`:: configures Hibernate's bootstrap `BootstrapServiceRegistry` which manages class-loading, etc. `@BootstrapServiceRegistry` is used to provide Java services and Hibernate `Integrator` implementations for the test. +`@ServiceRegistry`:: configures Hibernate's standard `StandardServiceRegistry`. `@ServiceRegistry` is used to provide settings, contributors, services, etc. + +Also exposes `ServiceRegistryScope` via JUnit 5 `ParameterResolver`. `ServiceRegistryScope` allows +access to the managed `ServiceRegistry` from tests and callbacks. + +``` +@BootstrapServiceRegistry( + javaServices=@JavaServices( + role=TypeContributions.class, + impls=CustomTypeContributions.class + ), + ... +) +@ServiceRegistry( + settings=@Setting( + name="hibernate.show_sql", + value="true" + ), + services=@Service( + role=ConnectionProvider.class, + impl=CustomConnectionProvider.class + ), + ... +) +class TheTest { + @Test void testIt(ServiceRegistryScope scope) { + StandardServiceRegistry reg = scope.getRegistry(); + ... + } +} +``` + + +[[junit5-domain-model]] +=== DomainModelExtension + +Manages the domain model for the test as part of its lifecycle. + +`@DomainModel`:: defines the sources of the domain model used in the test - type contributions, managed classes, XML mappings, etc. + +If available, this extension uses the `ServiceRegistry` instances available from <>. + +Exposes `DomainModelScope` via JUnit5 `ParameterResolver`, allowing access to details about the domain model from the `org.hibernate.mapping` "boot model". + + +``` +@DomainModel( + standardDomainModels=StandardDomainModel.ANIMAL, + annotatedClasses={Entity1.class, Entity2.class}, + xmlMappings="resource/path/to/my-mapping.xml", + ... +) +class TheTest { + @Test void testIt(DomainModelScope scope) { + MetadataImplementor meta = scope.getDomainModel(); + ... + + PersistentClass entityMapping = scope.getEntityBinding(Entity1.class); + ... + + scope.withHierarchy(Entity1.class, (entityMapping) -> { + ... + } + } +} +``` + + +=== SessionFactoryExtension + +Manages a Hibernate `SessionFactory` as part of the test lifecycle. + +`@SessionFactory`:: is used to configure the runtime aspects of the `SessionFactory` fixture. + +If available, uses the `ServiceRegistry` instances available from <> as well +as the domain model defined by <>. + +Exposes `SessionFactoryScope` via JUnit5 `ParameterResolver`. + +``` +@SessionFactory( + generateStatistics=true, + exportSchema=true, + useCollectingStatementInspector=true, + ... +) +class TheTest { + @Test void testIt(SessionFactoryScope scope) { + SQLStatementInspector sqlCollector = scope.getCollectingStatementInspector(); + sqlCollector.clear(); + + scope.inTransaction( (session) -> { + ... + assertThat(sqlCollector.getSqlQueries()).isEmpty(); + } ); + + Entity1 e = scope.fromTransaction( (session) -> { + Entity1 it = session.find(Entity1.class, id); + ... + return it; + } ); + } +} +``` + +=== DialectFilterExtension + +Allows filtering tests based on Dialect used. Implemented as a JUnit `ExecutionCondition` which is used to dynamically determine whether a test should be run. Used in conjunction with: + +`@RequiresDialect`:: says to only run this test for the given Dialect(s). +`@SkipForDialect`:: says to skip this test for the given Dialect(s). + +=== ExpectedExceptionExtension + +Used with `@ExpectedException` to allow testing that an excepted exception occurs as the "success" condition. + +``` +@DomainModel(...) +@SessionFactory(...) +class TheTest { + @Test + @ExpectedException(UnknownEntityTypeException.class) + void testIt(SessionFactoryScope) { + scope.inTransaction( (session) -> { + // Should fail as MyEmbeddable is not an entity + session.find(MyEmbeddable.class, 1); + } ); + } +} +``` + + +=== FailureExpectedExtension + +Used with `@FailureExpected` to indicate that a test is (currently) expected to fail. You might use this, e.g., for a test that is the reproducer for a bug report before working on it. It basically just flips the success/failure condition. In fact, a test marked with `@FailureExpected` will be marked a failure if it succeeds. + +``` +@Test +@JiraKey("HHH-123456789") +@FailureExpected +void bugReproducer(...) {...} +``` + + +=== LoggingInspectionsExtension and MessageKeyInspectionExtension + +Both are used for testing log messages. + +`@LoggingInspections`:: used to watch more than one "message key". +`MessageKeyInspection`:: used to watch a single "message key". + + +=== EntityManagerExtension + +Used in conjunction with `@Jpa` to build tests with an `EntityManagerFactory` fixture. + +Since Hibernate's `SessionFactory` *is a* `EntityManagerFactory`, `@BootstrapServiceRegistry`, `@ServiceRegistry`, `@DomainModel` and `@SessionFactory` can also be used to perform tests with a (`SessionFactory` as a) `EntityManagerFactory` fixture. + +The distinction with `@Jpa` is that `EntityManagerExtension` uses the JPA-defined bootstrap APIs. How the +`SessionFactory` is built is the difference. + + +== JUnit 4 + +Historically, Hibernate used JUnit 4 for its test suite. Since the release of https://junit.org/junit5/[JUnit 5], we've moved to using the testing approach outlined in <>. However, many existing tests still use the legacy JUnit 4 based infrastructure (boilerplate) based on "test templates". + + +=== Test templates The Hibernate team maintains a set of "test templates" intended to help developers write tests. These test templates are maintained in GitHub @ https://github.com/hibernate/hibernate-test-case-templates/tree/main/orm[hibernate-test-case-templates] @@ -22,7 +203,7 @@ The Hibernate team maintains a set of "test templates" intended to help develope NOTE: the test templates are generally not a good starting point for problems building the SessionFactory/EntityManager. In JUnit terms they manage the SessionFactory/EntityManager as set-up and teardown constructs._ -== Annotations +=== Annotations When using "test templates" you can annotate a single test or a whole test class with one of the following annotations: From 2c55a1feb10ef57b370648ce1ba9f3b4edf0ab87 Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Wed, 15 Feb 2023 21:19:37 +0100 Subject: [PATCH 0091/1497] HHH-16082 - Correction to the epoch extraction for DB2 Signed-off-by: Jan Schatteman --- .../src/main/java/org/hibernate/dialect/DB2Dialect.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 9306dbfe1661..e1b5a2b4eabb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DB2Dialect.java @@ -1012,6 +1012,12 @@ public String extractPattern(TemporalUnit unit) { return "dayofweek(?2)"; case QUARTER: return "quarter(?2)"; + case EPOCH: + if ( getDB2Version().isBefore( 11 ) ) { + return timestampdiffPattern( TemporalUnit.SECOND, TemporalType.TIMESTAMP, TemporalType.TIMESTAMP ) + .replace( "?2", "'1970-01-01 00:00:00'" ) + .replace( "?3", "?2" ); + } } return super.extractPattern( unit ); } From f2deb8f58e990ee87b6e00a736f6549d092da3d0 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Tue, 14 Feb 2023 12:03:47 +0100 Subject: [PATCH 0092/1497] HHH-14514 Fix auto eviction of collection cache --- .../internal/CollectionCacheInvalidator.java | 12 +- ...oncurrencyCollectionCacheEvictionTest.java | 269 ++++++++++++++++++ 2 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/cache/TransactionalConcurrencyCollectionCacheEvictionTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java index 85d3d189644a..b8d11ee46358 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/internal/CollectionCacheInvalidator.java @@ -181,13 +181,14 @@ private void evict(Object id, CollectionPersister collectionPersister, EventSour if ( LOG.isDebugEnabled() ) { LOG.debug( "Evict CollectionRegion " + collectionPersister.getRole() + " for id " + id ); } - AfterTransactionCompletionProcess afterTransactionProcess = new CollectionEvictCacheAction( + CollectionEvictCacheAction evictCacheAction = new CollectionEvictCacheAction( collectionPersister, null, id, session - ).lockCache(); - session.getActionQueue().registerProcess( afterTransactionProcess ); + ); + evictCacheAction.execute(); + session.getActionQueue().registerProcess( evictCacheAction.getAfterTransactionCompletionProcess() ); } //execute the same process as invalidation with collection operations @@ -202,11 +203,8 @@ private static final class CollectionEvictCacheAction extends CollectionAction { @Override public void execute() throws HibernateException { - } - - public AfterTransactionCompletionProcess lockCache() { beforeExecutions(); - return getAfterTransactionCompletionProcess(); + evict(); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cache/TransactionalConcurrencyCollectionCacheEvictionTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/TransactionalConcurrencyCollectionCacheEvictionTest.java new file mode 100644 index 000000000000..ba23e9b01b4d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cache/TransactionalConcurrencyCollectionCacheEvictionTest.java @@ -0,0 +1,269 @@ +/* + * 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.orm.test.cache; + +import java.util.HashSet; +import java.util.Set; +import jakarta.persistence.Access; +import jakarta.persistence.AccessType; +import jakarta.persistence.Cacheable; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.cache.internal.CollectionCacheInvalidator; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; + +/** + * @author Christian Beikov + */ +@TestForIssue(jiraKey = "HHH-4910") +public class TransactionalConcurrencyCollectionCacheEvictionTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Person.class, Phone.class }; + } + + @Before + public void before() { + CollectionCacheInvalidator.PROPAGATE_EXCEPTION = true; + } + + @After + public void after() { + CollectionCacheInvalidator.PROPAGATE_EXCEPTION = false; + } + + @Override + protected void configure(Configuration cfg) { + super.configure( cfg ); + cfg.setProperty( Environment.AUTO_EVICT_COLLECTION_CACHE, "true" ); + cfg.setProperty( Environment.USE_SECOND_LEVEL_CACHE, "true" ); + cfg.setProperty( Environment.USE_QUERY_CACHE, "false" ); + } + + @Override + protected void prepareTest() throws Exception { + doInHibernate( + this::sessionFactory, + s -> { + Person bart = new Person( 1L, "Bart" ); + Person lisa = new Person( 2L, "Lisa" ); + Person maggie = new Person( 3L, "Maggie" ); + s.persist( bart ); + s.persist( lisa ); + s.persist( maggie ); + + bart.addPhone( "0-1122334455" ); + bart.addPhone( "0-2233445566" ); + bart.addPhone( "0-3344556677" ); + bart.addPhone( "0-4455667788" ); + bart.addPhone( "0-5566778899" ); + + lisa.addPhone( "1-1122334455" ); + lisa.addPhone( "1-2233445566" ); + lisa.addPhone( "1-3344556677" ); + lisa.addPhone( "1-4455667788" ); + lisa.addPhone( "1-5566778899" ); + + maggie.addPhone( "2-1122334455" ); + maggie.addPhone( "2-2233445566" ); + maggie.addPhone( "2-3344556677" ); + maggie.addPhone( "2-4455667788" ); + maggie.addPhone( "2-5566778899" ); + + bart.getPhones().forEach( s::persist ); + lisa.getPhones().forEach( s::persist ); + maggie.getPhones().forEach( s::persist ); + } + ); + } + + @Override + protected void cleanupTest() throws Exception { + doInHibernate( + this::sessionFactory, + s -> { + s.createQuery( "delete from Phone" ).executeUpdate(); + s.createQuery( "delete from Person" ).executeUpdate(); + } + ); + } + + @Test + public void testCollectionCacheEvictionInsert() { + doInHibernate( + this::sessionFactory, + s -> { + Person bart = s.find( Person.class, 1L ); + assertEquals( 5, bart.getPhones().size() ); + s.persist( new Phone( "test", bart ) ); + } + ); + doInHibernate( + this::sessionFactory, + s -> { + Person bart = s.find( Person.class, 1L ); + assertEquals( 6, bart.getPhones().size() ); + } + ); + } + + @Test + public void testCollectionCacheEvictionRemove() { + Long phoneId = doInHibernate( + this::sessionFactory, + s -> { + Person bart = s.find( Person.class, 1L ); + // Lazy load phones + assertEquals( 5, bart.getPhones().size() ); + return bart.getPhones().iterator().next().getId(); + } + ); + doInHibernate( + this::sessionFactory, + s -> { + s.remove( s.getReference( Phone.class, phoneId ) ); + } + ); + doInHibernate( + this::sessionFactory, + s -> { + Person bart = s.find( Person.class, 1L ); + assertEquals( 4, bart.getPhones().size() ); + } + ); + } + + @Entity(name = "Person") + @Table(name = "PERSON") + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + public static class Person { + + @Id + @Access(value = AccessType.PROPERTY) + @Column(name = "PERSONID", nullable = false) + private Long id; + + @Column(name = "NAME") + private String name; + + @OneToMany(fetch = FetchType.LAZY, mappedBy = "person") + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + private final Set phones = new HashSet<>(); + + public Person() { + } + + public Person(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public Set getPhones() { + return phones; + } + + public Phone addPhone(String number) { + Phone phone = new Phone( number, this ); + getPhones().add( phone ); + return phone; + } + } + + @Entity(name = "Phone") + @Table(name = "PHONE") + @Cacheable + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) + public static class Phone { + + @Id + @Access(value = AccessType.PROPERTY) + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "PHONEID", nullable = false) + private Long id; + + @Column(name = "PHONE_NUMBER") + private String number; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "PERSONID") + private Person person; + + public Phone() { + } + + public Phone(String number, Person person) { + this.number = number; + this.person = person; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public Person getPerson() { + return person; + } + + public void setPerson(Person person) { + this.person = person; + } + } + +} From f16a28b95a38b03dd54dfc1d331d70aec992389e Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 16 Feb 2023 19:43:32 +0000 Subject: [PATCH 0093/1497] HHH-16190 Update the GraalVM module to match the reflective needs of StandardStack --- .../graalvm/internal/QueryParsingSupport.java | 157 ------------------ .../graalvm/internal/StaticClassLists.java | 39 ++++- 2 files changed, 38 insertions(+), 158 deletions(-) delete mode 100644 hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/QueryParsingSupport.java diff --git a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/QueryParsingSupport.java b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/QueryParsingSupport.java deleted file mode 100644 index ab2da56dd1f9..000000000000 --- a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/QueryParsingSupport.java +++ /dev/null @@ -1,157 +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 . - */ -package org.hibernate.graalvm.internal; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.hibernate.internal.build.AllowSysOut; -import org.hibernate.internal.util.ReflectHelper; - -import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.RuntimeReflection; - -/** - * This registers all ANTLR parser nodes for reflection, something that is necessary - * as the HQL parser's inner workings are based on reflection. - * This is different than the "static" registrations of {@link GraalVMStaticFeature} - * as we only register these if the HQL parser is actually reachable: some particularly - * simple applications might not need dynamic queries being expressed in string form, - * and for such cases the reflective registrations can be skipped. - * - * At time of writing, this is particularly unlikely to be effective as Hibernate ORM - * requires the parsers during bootstrap, but there is reasonable hope that this might - * be improved on, and can already be used by framework integrations which are able - * to bypass the traditional boot sequence. - * - * @author Sanne Grinovero - */ -public final class QueryParsingSupport implements Feature { - - private final AtomicBoolean triggered = new AtomicBoolean( false); - - /** - * To set this, add `-J-Dorg.hibernate.graalvm.diagnostics=true` to the native-image parameters - */ - private static final boolean log = Boolean.getBoolean( "org.hibernate.graalvm.diagnostics" ); - - @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - Class lexerClazz = access.findClassByName("org.hibernate.grammars.hql.HqlLexer"); - Class parserClazz = access.findClassByName("org.hibernate.grammars.hql.HqlParser"); - access.registerReachabilityHandler(this::enableHQLSupport, lexerClazz); - access.registerReachabilityHandler(this::enableHQLSupport, parserClazz); - } - - @Override - public String getDescription() { - return "Hibernate ORM's support for HQL Parser in GraalVM"; - } - - @AllowSysOut - private void enableHQLSupport(DuringAnalysisAccess duringAnalysisAccess) { - final boolean needsEnablingYet = triggered.compareAndSet( false, true ); - if ( needsEnablingYet ) { - if ( log ) { - System.out.println( "Hibernate ORM 's automatic feature for GraalVM native images: enabling support for HQL query parsing" ); - } - enableAntlrParsersSupport(); - } - } - - private void enableAntlrParsersSupport() { - final Class[] needsHavingSimpleConstructors = typesNeedingDefaultConstructorAccessible(); - final Class[] neddingAllConstructorsAccessible = typesNeedingAllConstructorsAccessible(); - //Size formula is just a reasonable guess: - ArrayList executables = new ArrayList<>( needsHavingSimpleConstructors.length + neddingAllConstructorsAccessible.length * 3 ); - for ( Class c : needsHavingSimpleConstructors ) { - executables.add( ReflectHelper.getDefaultConstructor( c ) ); - } - for ( Class c : neddingAllConstructorsAccessible ) { - for ( Constructor declaredConstructor : c.getDeclaredConstructors() ) { - executables.add( declaredConstructor ); - } - } - RuntimeReflection.register( needsHavingSimpleConstructors ); - RuntimeReflection.register( neddingAllConstructorsAccessible ); - RuntimeReflection.register( executables.toArray(new Executable[0]) ); - } - - public static Class[] typesNeedingAllConstructorsAccessible() { - return new Class[] { - //ANTLR special ones: -// org.hibernate.hql.internal.ast.tree.EntityJoinFromElement.class, -// org.hibernate.hql.internal.ast.tree.MapKeyEntityFromElement.class, -// org.hibernate.hql.internal.ast.tree.ComponentJoin.class, - }; - } - - public static Class[] typesNeedingDefaultConstructorAccessible() { - return new Class[] { - //Support for @OrderBy -// org.hibernate.sql.ordering.antlr.NodeSupport.class, -// org.hibernate.sql.ordering.antlr.OrderByFragment.class, -// org.hibernate.sql.ordering.antlr.SortSpecification.class, -// org.hibernate.sql.ordering.antlr.OrderingSpecification.class, -// org.hibernate.sql.ordering.antlr.CollationSpecification.class, -// org.hibernate.sql.ordering.antlr.SortKey.class, - - //ANTLR tokens: -// antlr.CommonToken.class, -// org.hibernate.hql.internal.ast.tree.SelectClause.class, -// org.hibernate.hql.internal.ast.tree.HqlSqlWalkerNode.class, -// org.hibernate.hql.internal.ast.tree.MethodNode.class, -// org.hibernate.hql.internal.ast.tree.UnaryLogicOperatorNode.class, -// org.hibernate.hql.internal.ast.tree.NullNode.class, -// org.hibernate.hql.internal.ast.tree.IntoClause.class, -// org.hibernate.hql.internal.ast.tree.UpdateStatement.class, -// org.hibernate.hql.internal.ast.tree.SelectExpressionImpl.class, -// org.hibernate.hql.internal.ast.tree.CastFunctionNode.class, -// org.hibernate.hql.internal.ast.tree.DeleteStatement.class, -// org.hibernate.hql.internal.ast.tree.SqlNode.class, -// org.hibernate.hql.internal.ast.tree.SearchedCaseNode.class, -// org.hibernate.hql.internal.ast.tree.FromElement.class, -// org.hibernate.hql.internal.ast.tree.JavaConstantNode.class, -// org.hibernate.hql.internal.ast.tree.SqlFragment.class, -// org.hibernate.hql.internal.ast.tree.MapKeyNode.class, -// org.hibernate.hql.internal.ast.tree.ImpliedFromElement.class, -// org.hibernate.hql.internal.ast.tree.IsNotNullLogicOperatorNode.class, -// org.hibernate.hql.internal.ast.tree.InsertStatement.class, -// org.hibernate.hql.internal.ast.tree.UnaryArithmeticNode.class, -// org.hibernate.hql.internal.ast.tree.CollectionFunction.class, -// org.hibernate.hql.internal.ast.tree.BinaryLogicOperatorNode.class, -// org.hibernate.hql.internal.ast.tree.CountNode.class, -// org.hibernate.hql.internal.ast.tree.IsNullLogicOperatorNode.class, -// org.hibernate.hql.internal.ast.tree.IdentNode.class, -// org.hibernate.hql.internal.ast.tree.ParameterNode.class, -// org.hibernate.hql.internal.ast.tree.MapEntryNode.class, -// org.hibernate.hql.internal.ast.tree.MapValueNode.class, -// org.hibernate.hql.internal.ast.tree.InLogicOperatorNode.class, -// org.hibernate.hql.internal.ast.tree.IndexNode.class, -// org.hibernate.hql.internal.ast.tree.DotNode.class, -// org.hibernate.hql.internal.ast.tree.ResultVariableRefNode.class, -// org.hibernate.hql.internal.ast.tree.BetweenOperatorNode.class, -// org.hibernate.hql.internal.ast.tree.AggregateNode.class, -// org.hibernate.hql.internal.ast.tree.QueryNode.class, -// org.hibernate.hql.internal.ast.tree.BooleanLiteralNode.class, -// org.hibernate.hql.internal.ast.tree.SimpleCaseNode.class, -// org.hibernate.hql.internal.ast.tree.OrderByClause.class, -// org.hibernate.hql.internal.ast.tree.FromClause.class, -// org.hibernate.hql.internal.ast.tree.ConstructorNode.class, -// org.hibernate.hql.internal.ast.tree.LiteralNode.class, -// org.hibernate.hql.internal.ast.tree.BinaryArithmeticOperatorNode.class, - - //Special tokens: -// org.hibernate.hql.internal.ast.HqlToken.class, -// org.hibernate.hql.internal.ast.tree.Node.class, - - }; - } - -} diff --git a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java index f7eed53749cb..7197f4291736 100644 --- a/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java +++ b/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java @@ -6,6 +6,24 @@ */ package org.hibernate.graalvm.internal; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.hibernate.graph.internal.parse.SubGraphGenerator; +import org.hibernate.graph.spi.AttributeNodeImplementor; +import org.hibernate.graph.spi.GraphImplementor; +import org.hibernate.query.hql.spi.DotIdentifierConsumer; +import org.hibernate.query.hql.spi.SqmCreationProcessingState; +import org.hibernate.query.sqm.spi.ParameterDeclarationContext; +import org.hibernate.query.sqm.sql.FromClauseIndex; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.spi.SqlAstProcessingState; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.sql.ast.tree.select.QueryPart; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingState; import org.hibernate.tool.schema.internal.script.MultiLineSqlScriptExtractor; import org.hibernate.type.EnumType; @@ -76,7 +94,26 @@ public static Class[] typesNeedingArrayCopy() { org.hibernate.event.spi.PreCollectionUpdateEventListener[].class, org.hibernate.event.spi.PostCollectionRecreateEventListener[].class, org.hibernate.event.spi.PostCollectionRemoveEventListener[].class, - org.hibernate.event.spi.PostCollectionUpdateEventListener[].class + org.hibernate.event.spi.PostCollectionUpdateEventListener[].class, + //And other array types, necessary for allocation of generified instances of org.hibernate.internal.util.collections.StandardStack: + //TODO can this list be tested for consistency with the core module? Or generated? e.g. could use Jandex? + AttributeNodeImplementor[].class, + Clause[].class, + DotIdentifierConsumer[].class, + FetchParent[].class, + FromClauseIndex[].class, + Function[].class, + GraphImplementor[].class, + JdbcValuesSourceProcessingState[].class, + List[].class, + Map.Entry[].class, + ParameterDeclarationContext[].class, + QueryPart[].class, + SqlAstProcessingState[].class, + SqmCreationProcessingState[].class, + Statement[].class, + SubGraphGenerator[].class, + Supplier[].class, }; } From fd649a9b4e8474e56a0054c4c84a60177a856acd Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 16 Feb 2023 21:00:33 -0600 Subject: [PATCH 0094/1497] Misc --- design/doc-query-expressions.adoc | 33 ------------------- design/doc-temporal.adoc | 10 ------ design/{working => }/fk.adoc | 2 +- design/sql-ast.adoc | 7 ++-- design/working/6.0-posts.adoc | 4 +-- .../util/collections/CollectionHelper.java | 24 ++++++++++++++ 6 files changed, 31 insertions(+), 49 deletions(-) delete mode 100644 design/doc-query-expressions.adoc delete mode 100644 design/doc-temporal.adoc rename design/{working => }/fk.adoc (95%) diff --git a/design/doc-query-expressions.adoc b/design/doc-query-expressions.adoc deleted file mode 100644 index 7e79333c9bcb..000000000000 --- a/design/doc-query-expressions.adoc +++ /dev/null @@ -1,33 +0,0 @@ -= Query - -== Expressions - -=== Paths - -=== Literals - -=== Parameters - -=== Unary expressions - -=== Arithmetic operations - -Numeric v. temporal - -=== Functions - -- Standard functions -- SqmFunctionRegistry -- Role of Dialect - -=== Concatenation operations - -=== Entity-type references - -=== CASE statements - -=== COALESCE statements - -=== NULLIF statements - -=== Sub-queries \ No newline at end of file diff --git a/design/doc-temporal.adoc b/design/doc-temporal.adoc deleted file mode 100644 index ff21ddba9879..000000000000 --- a/design/doc-temporal.adoc +++ /dev/null @@ -1,10 +0,0 @@ -`OffsetDateTime` is not safe to store in database. This form does not understand "zone rules" relating to things -such as DST. An offset of +5, e.g., does not change when DST starts/ends - its just +5. - -A `ZonedDateTime` on the other hand knows the actual timezone as well as the offset for the LocalDateTime portion in -that timezone. It is much more complete picture of the actual Instant. - -The proper solution for storing "with tz" would be to always use a `ZonedDateTime`, converted from `OffsetDateTime` -if needed. In this case, I assume we need to transform a `LocalDateTime` to `ZonedDateTime`? - -^^ what about Dialects that do not support "with tz" datatype variants? Are there any anymore? diff --git a/design/working/fk.adoc b/design/fk.adoc similarity index 95% rename from design/working/fk.adoc rename to design/fk.adoc index 826cc7d1a0f3..c160126912f4 100644 --- a/design/working/fk.adoc +++ b/design/fk.adoc @@ -31,7 +31,7 @@ Assuming bi-directionality, we have 2 `Association` refs: -There is a single ForeignKeyDescriptor instance for this FK in our metamodel, with 2 Sides: +There is a single `ForeignKeyDescriptor` instance for this FK in our metamodel, with 2 Sides: ``` ForeignKeyDescriptor ( diff --git a/design/sql-ast.adoc b/design/sql-ast.adoc index 8e4d44fde81f..0d869e604212 100644 --- a/design/sql-ast.adoc +++ b/design/sql-ast.adoc @@ -30,10 +30,11 @@ The actual tree nodes are defined in the `org.hibernate.sql.ast.tree` package. == Building SQL AST -There are 2 main producers of SQL AST atm: +There are 3 main producers of SQL AST: -* SQM translation - see `org.hibernate.query.sqm.sql` -* metamodel-based loading - see `org.hibernate.loader.internal.MetamodelSelectBuilderProcess` +SQM:: Translation of HQL and criteria queries. See `org.hibernate.query.sqm.sql` +Loading:: SQL generated for persistence-context events to load entities and collections. This includes `Session#find`, `Session#get`, `Session#lock`, ... See `org.hibernate.loader.internal.MetamodelSelectBuilderProcess` +Mutations:: SQL generated for persistence-context flush events to write entity and collection state to the database. See `org.hibernate.persister.entity.mutation` and `org.hibernate.persister.collection.mutation` == Translating SQL AST diff --git a/design/working/6.0-posts.adoc b/design/working/6.0-posts.adoc index d1d1ae52d7cd..690f6780b8a7 100644 --- a/design/working/6.0-posts.adoc +++ b/design/working/6.0-posts.adoc @@ -37,7 +37,7 @@ from the removal of deprecated stuff. There are a few one-off changes that brea source compatibility; these are covered in the link:{migration-guide-url}[migration guide]. One specific change to note is that many of these contracts have been better defined with type -parameters. Theses were inconsistently and sometimes poorly defined in previous versions. +parameters. Theses were inconsistently (and sometimes poorly) defined in previous versions. Quite a few SPI contracts have changed to support many of the topics discussed here as well as in the link:{migration-guide-url}[migration guide]. Many will also be the subject of the mentioned @@ -267,4 +267,4 @@ For additional details, see: - the link:{migration-guide-url}[Migration Guide] - the https://hibernate.org/orm/releases/6.0/[release page]. -To get in touch, use the usual channels as discussed on the https://hibernate.org/community/[website]. \ No newline at end of file +To get in touch, use the usual channels as discussed on the https://hibernate.org/community/[website]. diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java index 00a13f9dbdb0..2fc6faca75ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java @@ -7,6 +7,7 @@ package org.hibernate.internal.util.collections; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -275,6 +276,10 @@ public static boolean isEmpty(Object[] objects) { return objects == null || objects.length == 0; } + public static boolean isNotEmpty(Object[] objects) { + return objects != null && objects.length > 0; + } + public static List listOf(T value1) { final List list = new ArrayList<>( 1 ); list.add( value1 ); @@ -418,6 +423,17 @@ public static Map toMap(Object... pairs) { return result; } + public static Map toSettingsMap(Object... pairs) { + assert pairs.length % 2 == 0; + if ( pairs.length == 2 ) { + return Collections.singletonMap( (String) pairs[0], pairs[1] ); + } + + final Map result = new HashMap<>(); + applyToMap( result, pairs ); + return result; + } + @SuppressWarnings({ "unchecked", "rawtypes" }) private static void applyToMap(Map map, Object... pairs) { assert pairs.length % 2 == 0; @@ -472,4 +488,12 @@ public static List combine(List... lists) { public static int size(List values) { return values == null ? 0 : values.size(); } + + public static Set toSet(X... values) { + final HashSet result = new HashSet<>(); + if ( isNotEmpty( values ) ) { + result.addAll( Arrays.asList( values ) ); + } + return result; + } } From 0b0799d567046793f4268167140819d6e9ed9f42 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Fri, 17 Feb 2023 14:50:51 +0000 Subject: [PATCH 0095/1497] HHH-16194 Failure to automatically integrate with Bean Validation is excessively noisy --- .../org/hibernate/boot/beanvalidation/TypeSafeActivator.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java index 6b02747b7ef0..f1a5c40833e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java @@ -483,11 +483,6 @@ private static ValidatorFactory getValidatorFactory(ActivationContext activation return Validation.buildDefaultValidatorFactory(); } catch ( Exception e ) { - LOG.infof( - e, - "Error calling `%s`", - "jakarta.validation.Validation#buildDefaultValidatorFactory" - ); throw new IntegrationException( "Unable to build the default ValidatorFactory", e ); } } From ad6dce7e89358a32bfe0552a54adeb732df5fc52 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 13 Feb 2023 14:42:56 +0100 Subject: [PATCH 0096/1497] HHH-16155 Add test for issue --- .../GeneratedAnnotationBatchTest.java | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/GeneratedAnnotationBatchTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/GeneratedAnnotationBatchTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/GeneratedAnnotationBatchTest.java new file mode 100644 index 000000000000..374eb3b3d802 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/generated/GeneratedAnnotationBatchTest.java @@ -0,0 +1,128 @@ +/* + * 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.orm.test.mapping.generated; + +import java.time.Instant; +import java.util.List; + +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.CurrentTimestamp; +import org.hibernate.annotations.Generated; +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.generator.EventType.INSERT; +import static org.hibernate.generator.EventType.UPDATE; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel(annotatedClasses = GeneratedAnnotationBatchTest.GeneratedEntity.class) +@ServiceRegistry(settings = @Setting(name = AvailableSettings.STATEMENT_BATCH_SIZE, value = "3")) +public class GeneratedAnnotationBatchTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + // generate more entities than batch_size to trigger both + // implicit and explicit batch execution + session.persist( new GeneratedEntity( "new_1" ) ); + session.persist( new GeneratedEntity( "new_2" ) ); + session.persist( new GeneratedEntity( "new_3" ) ); + session.persist( new GeneratedEntity( "new_4" ) ); + session.persist( new GeneratedEntity( "new_5" ) ); + } ); + } + + @AfterAll + public void tearDOwn(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from GeneratedEntity" ).executeUpdate() ); + } + + @Test + public void testInsert(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final List resultList = session.createQuery( + "select generatedProp from GeneratedEntity", + Integer.class + ) + .getResultList(); + assertThat( resultList ).hasSize( 5 ); + resultList.forEach( value -> assertThat( value ).isEqualTo( 1 ) ); + } ); + } + + @Test + public void testUpdate(SessionFactoryScope scope) { + final Instant originalInstant = scope.fromTransaction( session -> session.createQuery( + "from GeneratedEntity where id = 1L", + GeneratedEntity.class + ).getSingleResult().getUpdateTimestamp() ); + scope.inTransaction( session -> { + final List entities = session.createQuery( + "from GeneratedEntity", + GeneratedEntity.class + ).getResultList(); + entities.forEach( ge -> ge.setName( "updated" ) ); + session.flush(); // force update and retrieval of generated values + entities.forEach( ge -> assertThat( ge.getName() ).isEqualTo( "updated" ) ); + entities.forEach( ge -> assertThat( ge.getUpdateTimestamp() ).isAfter( originalInstant ) ); + } ); + } + + @Entity(name = "GeneratedEntity") + public static class GeneratedEntity { + @Id + @GeneratedValue + private Long id; + + private String name; + + @Generated(event = INSERT) + @ColumnDefault("1") + private Integer generatedProp; + + @CurrentTimestamp(event = { INSERT, UPDATE }) + private Instant updateTimestamp; + + public GeneratedEntity() { + } + + public GeneratedEntity(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getGeneratedProp() { + return generatedProp; + } + + public Instant getUpdateTimestamp() { + return updateTimestamp; + } + } +} From 7e83dba91fb9562a8da6f381d29287c1d5e519b5 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Thu, 16 Feb 2023 11:05:22 +0100 Subject: [PATCH 0097/1497] HHH-16155 Disable batching when generated properties are found --- .../entity/mutation/InsertCoordinator.java | 14 ++++++++++---- .../entity/mutation/UpdateCoordinatorStandard.java | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java index 464af8a4c9f6..3aca5a64209e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/InsertCoordinator.java @@ -54,10 +54,16 @@ public class InsertCoordinator extends AbstractMutationCoordinator { public InsertCoordinator(AbstractEntityPersister entityPersister, SessionFactoryImplementor factory) { super( entityPersister, factory ); - insertBatchKey = new BasicBatchKey( - entityPersister.getEntityName() + "#INSERT", - null - ); + if ( entityPersister.hasInsertGeneratedProperties() ) { + // disable batching in case of insert generated properties + insertBatchKey = null; + } + else { + insertBatchKey = new BasicBatchKey( + entityPersister.getEntityName() + "#INSERT", + null + ); + } if ( entityPersister.getEntityMetamodel().isDynamicInsert() ) { // the entity specified dynamic-insert - skip generating the diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java index d18ccc80e23b..8e752f7b2887 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java @@ -89,10 +89,16 @@ public UpdateCoordinatorStandard(AbstractEntityPersister entityPersister, Sessio // there are cases where we need the full static updates. this.staticUpdateGroup = buildStaticUpdateGroup(); this.versionUpdateGroup = buildVersionUpdateGroup(); - this.batchKey = new BasicBatchKey( - entityPersister.getEntityName() + "#UPDATE", - null - ); + if ( entityPersister.hasUpdateGeneratedProperties() ) { + // disable batching in case of update generated properties + this.batchKey = null; + } + else { + this.batchKey = new BasicBatchKey( + entityPersister.getEntityName() + "#UPDATE", + null + ); + } } @Override From 6b2281d0ba5923e132b9a661db151f204108a954 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 17 Feb 2023 13:14:34 +0200 Subject: [PATCH 0098/1497] Apply changes needed for support of @ValueGenerationType in Hibernate Reactive --- .../internal/GeneratedValuesProcessor.java | 20 +++++++++++++++++++ .../entity/AbstractEntityPersister.java | 10 ++++++++++ .../mutation/UpdateCoordinatorStandard.java | 8 ++++---- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java index 15f0c156b18a..adef7204b0c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/GeneratedValuesProcessor.java @@ -139,4 +139,24 @@ private void setEntityAttributes(Object entity, Object[] state, Object[] selecti attribute.getAttributeMetadata().getPropertyAccess().getSetter().set( entity, generatedValue ); } } + + public SelectStatement getSelectStatement() { + return selectStatement; + } + + public List getGeneratedValuesToSelect() { + return generatedValuesToSelect; + } + + public List getJdbcParameters() { + return jdbcParameters; + } + + public EntityMappingType getEntityDescriptor() { + return entityDescriptor; + } + + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index db063c9b905e..046b1067a786 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -1000,6 +1000,16 @@ public String getVersionSelectString() { return sqlVersionSelectString; } + @Internal + public GeneratedValuesProcessor getInsertGeneratedValuesProcessor() { + return insertGeneratedValuesProcessor; + } + + @Internal + public GeneratedValuesProcessor getUpdateGeneratedValuesProcessor() { + return updateGeneratedValuesProcessor; + } + @Override public boolean hasRowId() { return rowIdName != null; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java index 8e752f7b2887..504a88460640 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/UpdateCoordinatorStandard.java @@ -222,7 +222,7 @@ && entityPersister().hasLazyDirtyFields( dirtyAttributeIndexes ) ) { ); } - private void performUpdate( + protected void performUpdate( Object entity, Object id, Object rowId, @@ -300,7 +300,7 @@ else if ( valuesAnalysis.needsDynamicUpdate() ) { } } - private static int[] dirtyAttributeIndexes(int[] incomingDirtyIndexes, int[] preUpdateGeneratedIndexes) { + protected static int[] dirtyAttributeIndexes(int[] incomingDirtyIndexes, int[] preUpdateGeneratedIndexes) { if ( preUpdateGeneratedIndexes.length == 0 ) { return incomingDirtyIndexes; } @@ -362,7 +362,7 @@ private static boolean includedInLock( } } - private boolean handlePotentialImplicitForcedVersionIncrement( + protected boolean handlePotentialImplicitForcedVersionIncrement( Object entity, Object id, Object[] values, @@ -538,7 +538,7 @@ private int[] preUpdateInMemoryValueGeneration( * Transform the array of property indexes to an array of booleans for each attribute, * true when the property is dirty */ - private boolean[] getPropertiesToUpdate(final int[] dirtyProperties, final boolean hasDirtyCollection) { + protected boolean[] getPropertiesToUpdate(final int[] dirtyProperties, final boolean hasDirtyCollection) { final boolean[] updateability = entityPersister().getPropertyUpdateability(); if ( dirtyProperties == null ) { return updateability; From 965cb5ecbe10c16ca6a8cf80b3fbbb3cac2e0779 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Sun, 19 Feb 2023 16:47:12 +0000 Subject: [PATCH 0099/1497] HHH-16200 Upgrade to Narayana 6.0.0.Final and matching jboss-transaction-spi 8.0.0.Final --- settings.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle b/settings.gradle index 6e6a5826f82a..a4f9b2a5067e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -155,8 +155,8 @@ dependencyResolutionManagement { alias( "shrinkwrapDescriptors" ).to( "org.jboss.shrinkwrap.descriptors", "shrinkwrap-descriptors-impl-javaee" ).versionRef( "shrinkwrapDescriptors" ) alias( "shrinkwrapDescriptorsApi" ).to( "org.jboss.shrinkwrap.descriptors", "shrinkwrap-descriptors-api-javaee" ).versionRef( "shrinkwrapDescriptors" ) - alias( "jbossJta" ).to( "org.jboss.narayana.jta", "narayana-jta" ).version( "6.0.0.CR1" ) - alias( "jbossTxSpi" ).to( "org.jboss", "jboss-transaction-spi-jakarta" ).version( "7.6.1.Final" ) + alias( "jbossJta" ).to( "org.jboss.narayana.jta", "narayana-jta" ).version( "6.0.0.Final" ) + alias( "jbossTxSpi" ).to( "org.jboss", "jboss-transaction-spi" ).version( "8.0.0.Final" ) alias( "wildFlyTxnClient" ).to( "org.wildfly.transaction", "wildfly-transaction-client-jakarta" ).version( "2.0.0.Final" ) alias( "weld" ).to( "org.jboss.weld.se", "weld-se-shaded" ).version( "4.0.1.SP1" ) } From 66ef96532027ed4bc6045b79dfbb0ab39939d138 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 14 Feb 2023 16:35:11 +0100 Subject: [PATCH 0100/1497] HHH-16184 Add test for issue --- .../IterateOverListInTheSetMethodTest.java | 133 +++++++++++++++++- .../list/ParentChildMapping.hbm.xml | 8 +- 2 files changed, 131 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/IterateOverListInTheSetMethodTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/IterateOverListInTheSetMethodTest.java index 3e2c3998a382..94e02987368b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/IterateOverListInTheSetMethodTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/IterateOverListInTheSetMethodTest.java @@ -1,19 +1,40 @@ package org.hibernate.orm.test.collection.list; +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.query.spi.ScrollableResultsImplementor; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + @DomainModel( xmlMappings = "org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml" ) @SessionFactory public class IterateOverListInTheSetMethodTest { - @BeforeAll - public void setUp(SessionFactoryScope scope) { + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createMutationQuery( "delete from Child" ).executeUpdate(); + session.createMutationQuery( "delete from Parent" ).executeUpdate(); + } + ); + } + + + @Test + @TestForIssue(jiraKey = "HHH-16120") + public void testHqlQuery(SessionFactoryScope scope) { scope.inTransaction( session -> { Child child = new Child( 1, "Luigi" ); @@ -27,14 +48,114 @@ public void setUp(SessionFactoryScope scope) { session.persist( child2 ); } ); + scope.inTransaction( + session -> { + session.createQuery( "select p from Parent p", Parent.class ).list(); + } + ); } @Test - public void testHqlQuery(SessionFactoryScope scope) { - scope.inSession( + @TestForIssue(jiraKey = "HHH-16184") + public void testSelectParentsWithoutChildren(SessionFactoryScope scope) { + scope.inTransaction( session -> { - session.createQuery( "select p from Parent p" ).list(); + Parent parent = new Parent( 2, "Fabio" ); + session.persist( parent ); + } + ); + + SQLStatementInspector collectingStatementInspector = scope.getCollectingStatementInspector(); + collectingStatementInspector.clear(); + scope.inTransaction( + session -> { + session.createQuery( "select p from Parent p", Parent.class ).list(); + + } + ); + assertThat( collectingStatementInspector.getSqlQueries().size() ).isEqualTo( 2 ); + } + + @Test + @TestForIssue(jiraKey = "HHH-16184") + public void testScrollParentsWithoutChildren(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Parent parent = new Parent( 2, "Fabio" ); + session.persist( parent ); + } + ); + + SQLStatementInspector collectingStatementInspector = scope.getCollectingStatementInspector(); + collectingStatementInspector.clear(); + scope.inTransaction( + session -> { + try (ScrollableResultsImplementor results = session.createQuery( + "select p from Parent p", + Parent.class + ) + .scroll()) { + List list = new ArrayList<>(); + while ( results.next() ) { + list.add( results.get() ); + } + assertThat( list.size() ).isEqualTo( 1 ); + } + + } + ); + assertThat( collectingStatementInspector.getSqlQueries().size() ).isEqualTo( 2 ); + } + + @Test + @TestForIssue(jiraKey = "HHH-16184") + public void testSelectParentsWithoutChildren2(SessionFactoryScope scope) { + Integer parentId = 2; + scope.inTransaction( + session -> { + Parent parent = new Parent( parentId, "Fabio" ); + session.persist( parent ); + } + ); + + SQLStatementInspector collectingStatementInspector = scope.getCollectingStatementInspector(); + collectingStatementInspector.clear(); + scope.inTransaction( + session -> { + session.createQuery( "select p from Parent p where p.id = :id", Parent.class ) + .setParameter( "id", parentId ) + .uniqueResult(); + + } + ); + assertThat( collectingStatementInspector.getSqlQueries().size() ).isEqualTo( 2 ); + } + + @Test + @TestForIssue(jiraKey = "HHH-16184") + public void testSelectParentsWithChildren(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Child child = new Child( 1, "Luigi" ); + Child child2 = new Child( 2, "Franco" ); + Parent parent = new Parent( 2, "Fabio" ); + parent.addChild( child ); + parent.addChild( child2 ); + + session.persist( parent ); + session.persist( child ); + session.persist( child2 ); + } + ); + + SQLStatementInspector collectingStatementInspector = scope.getCollectingStatementInspector(); + collectingStatementInspector.clear(); + scope.inTransaction( + session -> { + session.createQuery( "select p from Parent p", Parent.class ).list(); + } ); + assertThat( collectingStatementInspector.getSqlQueries().size() ).isEqualTo( 2 ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml index 77bcc3ad02af..9e94d3bad72f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/collection/list/ParentChildMapping.hbm.xml @@ -3,17 +3,17 @@ "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - + - + - + - + \ No newline at end of file From fb901051caec95f50f0abf1b3e68e1da7dc2b0ca Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 14 Feb 2023 16:35:40 +0100 Subject: [PATCH 0101/1497] HHH-16184 Two queries are execute to initialize empty collections --- .../internal/ScrollableResultsImpl.java | 22 +++++++++++++------ .../JdbcSelectExecutorStandardImpl.java | 6 +++-- .../sql/results/spi/ListResultsConsumer.java | 3 ++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java index df2858e7b993..0c7304e6a0b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ScrollableResultsImpl.java @@ -7,6 +7,7 @@ package org.hibernate.internal; import org.hibernate.HibernateException; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.sql.results.internal.RowProcessingStateStandardImpl; import org.hibernate.sql.results.jdbc.internal.JdbcValuesSourceProcessingStateStandardImpl; @@ -122,15 +123,22 @@ private void prepareCurrentRow(boolean underlyingScrollSuccessful) { return; } - currentRow = getRowReader().readRow( - getRowProcessingState(), - getProcessingOptions() - ); + final PersistenceContext persistenceContext = getPersistenceContext().getPersistenceContext(); - getRowProcessingState().finishRowProcessing(); - getJdbcValuesSourceProcessingState().finishUp(); + persistenceContext.beforeLoad(); + try { + currentRow = getRowReader().readRow( + getRowProcessingState(), + getProcessingOptions() + ); - getRowProcessingState().getSession().getPersistenceContext().initializeNonLazyCollections(); + getRowProcessingState().finishRowProcessing(); + getJdbcValuesSourceProcessingState().finishUp(); + } + finally { + persistenceContext.afterLoad(); + } + persistenceContext.initializeNonLazyCollections(); afterScrollOperation(); } 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 6740f0ce871c..dbd30125916f 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 @@ -291,9 +291,11 @@ private T doExecuteQuery( } } + final SharedSessionContractImplementor session = executionContext.getSession(); + final boolean stats; long startTime = 0; - final StatisticsImplementor statistics = executionContext.getSession().getFactory().getStatistics(); + final StatisticsImplementor statistics = session.getFactory().getStatistics(); if ( executionContext.hasQueryExecutionToBeAddedToStatistics() && jdbcValues instanceof JdbcValuesResultSetImpl ) { stats = statistics.isStatisticsEnabled(); @@ -359,7 +361,7 @@ public boolean shouldReturnProxies() { final T result = resultsConsumer.consume( jdbcValues, - executionContext.getSession(), + session, processingOptions, valuesProcessingState, rowProcessingState, diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/ListResultsConsumer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/ListResultsConsumer.java index 989989323005..cf421eb297aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/ListResultsConsumer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/ListResultsConsumer.java @@ -153,6 +153,7 @@ public List consume( final QueryOptions queryOptions = rowProcessingState.getQueryOptions(); RuntimeException ex = null; try { + persistenceContext.beforeLoad(); persistenceContext.getLoadContexts().register( jdbcValuesSourceProcessingState ); final JavaType domainResultJavaType = resolveDomainResultJavaType( @@ -221,8 +222,8 @@ else if ( this.uniqueSemantic == UniqueSemantic.ASSERT ) { } finally { try { - jdbcValues.finishUp( session ); + persistenceContext.afterLoad(); persistenceContext.initializeNonLazyCollections(); } catch (RuntimeException e) { From 998bae579169e4018dd2b91dfd2463924f924906 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Mon, 20 Feb 2023 14:37:56 +0100 Subject: [PATCH 0102/1497] HHH-16119 Fix test typo --- .../NativeQueryDynamicInstantiationAndTupleResultTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/NativeQueryDynamicInstantiationAndTupleResultTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/NativeQueryDynamicInstantiationAndTupleResultTest.java index cad030b804dc..63cb1ef6b2ba 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/NativeQueryDynamicInstantiationAndTupleResultTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/NativeQueryDynamicInstantiationAndTupleResultTest.java @@ -31,7 +31,7 @@ } ) @SessionFactory -@TestForIssue(jiraKey = "HHH-16199") +@TestForIssue(jiraKey = "HHH-16119") public class NativeQueryDynamicInstantiationAndTupleResultTest { public static final String DEMO_NAME = "it is a demo demo"; From 7ab02c3e5fc2917d0cf62694e0217d5af85ae2af Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 20 Feb 2023 13:35:32 +0100 Subject: [PATCH 0103/1497] add tests for casts to/from OffsetDateTime --- .../orm/test/query/hql/FunctionTests.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 79693752bf00..14a296ff8be7 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 @@ -15,6 +15,7 @@ import org.hibernate.dialect.MySQLDialect; import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.SybaseDialect; import org.hibernate.dialect.TiDBDialect; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.orm.domain.StandardDomainModel; @@ -867,6 +868,19 @@ public void testCastFunction(SessionFactoryScope scope) { ); } + @Test + @SkipForDialect(dialectClass = DB2Dialect.class, matchSubTypes = true) + @SkipForDialect(dialectClass = DerbyDialect.class) + @SkipForDialect(dialectClass = SybaseDialect.class, matchSubTypes = true) + public void testCastToOffsetDatetime(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createQuery("select cast(datetime 1911-10-09 12:13:14-02:00 as String)", String.class).getSingleResult(); + session.createQuery("select cast('1911-10-09 12:13:14.123-02:00' as OffsetDateTime)", OffsetDateTime.class) + .getSingleResult(); + + }); + } + @Test public void testCastDoubleToString(SessionFactoryScope scope) { scope.inTransaction( From 3438d1c966e9e2a61c2a368157c0667879c721c4 Mon Sep 17 00:00:00 2001 From: Gavin Date: Mon, 20 Feb 2023 16:39:45 +0100 Subject: [PATCH 0104/1497] fix mistake in Oracle timestamp rendering we need to include the time zone --- .../src/main/java/org/hibernate/dialect/OracleDialect.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 420731729020..5b5aa96f1f51 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -1257,7 +1257,7 @@ public void appendDateTimeLiteral(SqlAppender appender, TemporalAccessor tempora // offset we need to use the ANSI syntax if ( precision == TemporalType.TIMESTAMP && temporalAccessor.isSupported( ChronoField.OFFSET_SECONDS ) ) { appender.appendSql( "timestamp '" ); - appendAsTimestampWithNanos( appender, temporalAccessor, supportsTemporalLiteralOffset(), jdbcTimeZone ); + appendAsTimestampWithNanos( appender, temporalAccessor, true, jdbcTimeZone, false ); appender.appendSql( '\'' ); } else { From d0c07bdf89ef2612920b44973d67c47d835f83cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Mon, 13 Feb 2023 12:01:47 +0100 Subject: [PATCH 0105/1497] HHH-16175 Test entity graphs when including either all attributes or no attributes --- .../IncludeAllOrNoneGraphTest.java | 215 ++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/IncludeAllOrNoneGraphTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/IncludeAllOrNoneGraphTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/IncludeAllOrNoneGraphTest.java new file mode 100644 index 000000000000..23d90dc15a55 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/IncludeAllOrNoneGraphTest.java @@ -0,0 +1,215 @@ +package org.hibernate.orm.test.entitygraph; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.List; + +import org.hibernate.Hibernate; +import org.hibernate.graph.GraphSemantic; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedEntityGraph; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; + +@DomainModel( + annotatedClasses = { + IncludeAllOrNoneGraphTest.RootEntity.class, + IncludeAllOrNoneGraphTest.ContainedEntity.class + } +) +@SessionFactory +@TestForIssue(jiraKey = "HHH-16175") +public class IncludeAllOrNoneGraphTest { + + @BeforeEach + void init(SessionFactoryScope scope) { + scope.inTransaction( session -> { + for ( long i = 0; i < 3; ++i ) { + RootEntity entity = new RootEntity( i * 100 ); + ContainedEntity containedEager = new ContainedEntity( i * 100 + 1 ); + entity.setContainedEager( containedEager ); + containedEager.setContainingEager( entity ); + + session.persist( containedEager ); + session.persist( entity ); + + ContainedEntity containedLazy = new ContainedEntity( i * 100 + 2 ); + entity.getContainedLazy().add( containedLazy ); + containedLazy.setContainingLazy( entity ); + + session.persist( containedLazy ); + } + } ); + } + @AfterEach + void cleanUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + List containedList = session.createQuery( + "select c from ContainedEntity c", ContainedEntity.class ).list(); + for ( ContainedEntity entity : containedList ) { + entity.setContainingLazy( null ); + } + } ); + scope.inTransaction( session -> { + session.createMutationQuery( "delete RootEntity" ).executeUpdate(); + session.createMutationQuery( "delete ContainedEntity" ).executeUpdate(); + } ); + } + + @Test + void includeAll_fetchGraph(SessionFactoryScope scope) { + // Before HHH-16175 gets fixed, this leads to AssertionError in StandardEntityGraphTraversalStateImpl.traverse + testLoadingWithGraph(scope, RootEntity.GRAPH_INCLUDE_ALL, GraphSemantic.FETCH, + true, true ); + } + + @Test + void includeAll_loadGraph(SessionFactoryScope scope) { + // Before HHH-16175 gets fixed, this leads to AssertionError in StandardEntityGraphTraversalStateImpl.traverse + testLoadingWithGraph(scope, RootEntity.GRAPH_INCLUDE_ALL, GraphSemantic.LOAD, + true, true ); + } + + @Test + void includeNone_fetchGraph(SessionFactoryScope scope) { + testLoadingWithGraph(scope, RootEntity.GRAPH_INCLUDE_NONE, GraphSemantic.FETCH, + false, false ); + } + + @Test + void includeNone_loadGraph(SessionFactoryScope scope) { + testLoadingWithGraph(scope, RootEntity.GRAPH_INCLUDE_NONE, GraphSemantic.LOAD, + true, false ); + } + + private void testLoadingWithGraph(SessionFactoryScope scope, String graphName, GraphSemantic graphSemantic, + boolean expectContainedEagerInitialized, boolean expectContainedLazyInitialized) { + scope.inTransaction( session -> { + assertThat( session.createQuery( "select e from RootEntity e where id in (:ids)", RootEntity.class ) + .applyGraph( session.getEntityGraph( graphName ), graphSemantic ) + .setFetchSize( 100 ) + .setParameter( "ids", List.of( 0L, 100L, 200L ) ) + .list() ) + .isNotEmpty() + .allSatisfy( loaded -> assertThat( Hibernate.isInitialized( loaded.getContainedEager() ) ) + .as( "Hibernate::isInitialized for .getContainedEager() on %s", loaded ) + .isEqualTo( expectContainedEagerInitialized ) ) + .allSatisfy( loaded -> assertThat( Hibernate.isInitialized( loaded.getContainedLazy() ) ) + .as( "Hibernate::isInitialized for .getContainedLazy() on %s", loaded ) + .isEqualTo( expectContainedLazyInitialized ) ); + } ); + } + + @Entity(name = "RootEntity") + @NamedEntityGraph( + name = RootEntity.GRAPH_INCLUDE_ALL, + includeAllAttributes = true + ) + @NamedEntityGraph( + name = RootEntity.GRAPH_INCLUDE_NONE + ) + static class RootEntity { + + public static final String GRAPH_INCLUDE_ALL = "graph-include-all"; + public static final String GRAPH_INCLUDE_NONE = "graph-include-none"; + + @Id + private Long id; + + @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) + private ContainedEntity containedEager; + + @OneToMany(mappedBy = "containingLazy", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + private List containedLazy = new ArrayList<>(); + + public RootEntity() { + } + + public RootEntity(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public ContainedEntity getContainedEager() { + return containedEager; + } + + public void setContainedEager(ContainedEntity containedEager) { + this.containedEager = containedEager; + } + + public List getContainedLazy() { + return containedLazy; + } + + public void setContainedLazy(List containedLazy) { + this.containedLazy = containedLazy; + } + } + + @Entity(name = "ContainedEntity") + static class ContainedEntity { + + @Id + private Long id; + + @OneToOne(mappedBy = "containedEager") + private RootEntity containingEager; + + @ManyToOne + private RootEntity containingLazy; + + + public ContainedEntity() { + } + + public ContainedEntity(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public RootEntity getContainingEager() { + return containingEager; + } + + public void setContainingEager(RootEntity containingEager) { + this.containingEager = containingEager; + } + + public RootEntity getContainingLazy() { + return containingLazy; + } + + public void setContainingLazy(RootEntity containingLazy) { + this.containingLazy = containingLazy; + } + } +} From 6d69318aae234407d30e34af1f1e21fb2af2b509 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 15 Feb 2023 00:00:20 +0100 Subject: [PATCH 0106/1497] AssertionError in StandardEntityGraphTraversalStateImpl.traverse when using entity graph --- .../org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java | 2 +- .../results/internal/StandardEntityGraphTraversalStateImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 0ff335a54abd..2b877c1c6107 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 @@ -7162,7 +7162,7 @@ public Fetch visitIdentifierFetch(EntityResultGraphNode fetchParent) { .getEntityMappingType() .getIdentifierMapping(); final Fetchable fetchableIdentifierMapping = (Fetchable) identifierMapping; - return createFetch( fetchParent, fetchableIdentifierMapping, true ); + return createFetch( fetchParent, fetchableIdentifierMapping, false ); } private Fetch createFetch(FetchParent fetchParent, Fetchable fetchable, Boolean isKeyFetchable) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphTraversalStateImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphTraversalStateImpl.java index c9992da180fb..48ddd782ec6f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphTraversalStateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardEntityGraphTraversalStateImpl.java @@ -69,7 +69,7 @@ public TraversalResult traverse(FetchParent fetchParent, Fetchable fetchable, bo final Class subgraphMapKey; if ( fetchable instanceof PluralAttributeMapping ) { - PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; + final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) fetchable; if ( exploreKeySubgraph ) { subgraphMap = attributeNode.getKeySubGraphMap(); From dd5a8c97af2e7a0401fb8c795505dc2c124a6cac Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Tue, 21 Feb 2023 09:00:46 -0600 Subject: [PATCH 0107/1497] Fixed up some SessionFactory-related deprecation warnings --- .../org/hibernate/engine/spi/SessionFactoryImplementor.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java index 4f374a7bf41e..abca701250d1 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java @@ -13,6 +13,7 @@ import org.hibernate.SessionFactory; import org.hibernate.SessionFactoryObserver; import org.hibernate.boot.model.relational.SqlStringGenerationContext; +import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.cache.spi.CacheImplementor; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -151,6 +152,11 @@ default MappingMetamodelImplementor getMappingMetamodel() { WrapperOptions getWrapperOptions(); + SessionFactoryOptions getSessionFactoryOptions(); + + FilterDefinition getFilterDefinition(String filterName); + + /** From ac9f47ae43dd6e2828f15054ec33df028a8ce3dd Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 21 Feb 2023 14:25:23 +0000 Subject: [PATCH 0108/1497] HHH-16214 Use a more efficient Map implementation in SqmFunctionRegistry --- .../CaseInsensitiveDictionary.java | 78 +++++++++++++++++++ .../sqm/function/SqmFunctionRegistry.java | 33 ++++---- .../testing/SpatialSessionFactoryAware.java | 3 +- 3 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/internal/util/collections/CaseInsensitiveDictionary.java diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CaseInsensitiveDictionary.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CaseInsensitiveDictionary.java new file mode 100644 index 000000000000..c476ad72466b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CaseInsensitiveDictionary.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.internal.util.collections; + +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; + +import org.hibernate.Internal; + +/** + * Wraps a ConcurrentHashMap having all keys as Strings + * and ensures all keys are lowercased. + * It does assume keys and arguments are never null, preferring to throw a NPE + * over adding unnecessary checks. + * The public exposed methods are similar to the ones on Map, but + * not all Map methods are exposed - only a selection we actually need; this + * implies it doesn't implement Map; nothing stops us to make it implement Map + * but at time of writing it seems unnecessary for our purposes. + * @param the type for the stored values. + */ +@Internal +public final class CaseInsensitiveDictionary { + + private final Map map = new ConcurrentHashMap<>(); + + public V get(final String key) { + return map.get( trueKey( key ) ); + } + + /** + * Contrary to traditional Map, we make the return unmodifiable. + * @return the map's keySet + */ + public Set unmodifiableKeySet() { + return Collections.unmodifiableSet( map.keySet() ); + } + + /** + * Contrary to traditional Map, we make the return unmodifiable. + * @return the map's entrySet + */ + public Set> unmodifiableEntrySet() { + return Collections.unmodifiableSet( map.entrySet() ); + } + + public V put(final String key, V value) { + return map.put( trueKey( key ), value ); + } + + public V remove(final String key) { + return map.remove( trueKey( key ) ); + } + + public boolean containsKey(final String key) { + return map.containsKey( trueKey( key ) ); + } + + private static String trueKey(final String key) { + return key.toLowerCase( Locale.ROOT ); + } + + public void clear() { + map.clear(); + } + + public void forEach(final BiConsumer action) { + map.forEach( action ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java index b861bb427a55..0341e3edbc4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/function/SqmFunctionRegistry.java @@ -6,11 +6,12 @@ */ package org.hibernate.query.sqm.function; -import java.util.AbstractMap; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.stream.Stream; +import org.hibernate.internal.util.collections.CaseInsensitiveDictionary; import org.hibernate.query.sqm.produce.function.FunctionParameterType; import org.hibernate.query.sqm.produce.function.NamedFunctionDescriptorBuilder; import org.hibernate.query.sqm.produce.function.PatternFunctionDescriptorBuilder; @@ -35,27 +36,31 @@ public class SqmFunctionRegistry { private static final Logger log = Logger.getLogger( SqmFunctionRegistry.class ); - private final Map functionMap = new TreeMap<>( CASE_INSENSITIVE_ORDER ); - private final Map alternateKeyMap = new TreeMap<>( CASE_INSENSITIVE_ORDER ); + private final CaseInsensitiveDictionary functionMap = new CaseInsensitiveDictionary<>(); + private final CaseInsensitiveDictionary alternateKeyMap = new CaseInsensitiveDictionary<>(); public SqmFunctionRegistry() { log.trace( "SqmFunctionRegistry created" ); } - public Map getFunctions() { - return functionMap; + public Set getValidFunctionKeys() { + return functionMap.unmodifiableKeySet(); } + /** + * Useful for diagnostics - not efficient: do not use in production code. + * + * @return + */ public Stream> getFunctionsByName() { - return Stream.concat( - functionMap.entrySet().stream(), - alternateKeyMap.entrySet().stream().map( - entry -> new AbstractMap.SimpleEntry<>( - entry.getKey(), - functionMap.get( entry.getValue() ) - ) - ) - ); + final Map sortedFunctionMap = new TreeMap<>( CASE_INSENSITIVE_ORDER ); + for ( Map.Entry e : functionMap.unmodifiableEntrySet() ) { + sortedFunctionMap.put( e.getKey(), e.getValue() ); + } + for ( Map.Entry e : alternateKeyMap.unmodifiableEntrySet() ) { + sortedFunctionMap.put( e.getKey(), functionMap.get( e.getValue() ) ); + } + return sortedFunctionMap.entrySet().stream(); } /** diff --git a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/SpatialSessionFactoryAware.java b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/SpatialSessionFactoryAware.java index e52b48a691aa..11e583e7c552 100644 --- a/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/SpatialSessionFactoryAware.java +++ b/hibernate-spatial/src/test/java/org/hibernate/spatial/testing/SpatialSessionFactoryAware.java @@ -35,8 +35,7 @@ public void injectSessionFactoryScope(SessionFactoryScope scope) { this.supportedFunctions = scope.getSessionFactory() .getQueryEngine() .getSqmFunctionRegistry() - .getFunctions() - .keySet(); + .getValidFunctionKeys(); if ( DialectContext.getDialect() instanceof H2Dialect ) { initH2GISExtensionsForInMemDb(); } From 16153adde99d5cad0ac370b6151be5fc198dc5d7 Mon Sep 17 00:00:00 2001 From: Davide D'Alto Date: Wed, 22 Feb 2023 21:13:48 +0000 Subject: [PATCH 0109/1497] HHH-16220 Add MutationExecutorStandard#getNonBatchedStatementGroup For Hibernate Reactive --- .../jdbc/mutation/internal/MutationExecutorStandard.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/mutation/internal/MutationExecutorStandard.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/mutation/internal/MutationExecutorStandard.java index 2e1999ba33c9..227bb5b0fcff 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/mutation/internal/MutationExecutorStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/mutation/internal/MutationExecutorStandard.java @@ -157,6 +157,10 @@ public MutationExecutorStandard( ); } + protected PreparedStatementGroup getNonBatchedStatementGroup() { + return nonBatchedStatementGroup; + } + @Override public JdbcValueBindings getJdbcValueBindings() { return valueBindings; From 56470f4f7c49eb97701b802fb4ff23209042582f Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Tue, 21 Feb 2023 10:38:12 +0100 Subject: [PATCH 0110/1497] HHH-16175 Fix sybase test failure --- .../IncludeAllOrNoneGraphTest.java | 50 ++++++------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/IncludeAllOrNoneGraphTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/IncludeAllOrNoneGraphTest.java index 23d90dc15a55..89a363b1ecf2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/IncludeAllOrNoneGraphTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/entitygraph/IncludeAllOrNoneGraphTest.java @@ -1,7 +1,5 @@ package org.hibernate.orm.test.entitygraph; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.ArrayList; import java.util.List; @@ -20,11 +18,12 @@ import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; import jakarta.persistence.NamedEntityGraph; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; +import static org.assertj.core.api.Assertions.assertThat; + @DomainModel( annotatedClasses = { IncludeAllOrNoneGraphTest.RootEntity.class, @@ -49,21 +48,14 @@ void init(SessionFactoryScope scope) { ContainedEntity containedLazy = new ContainedEntity( i * 100 + 2 ); entity.getContainedLazy().add( containedLazy ); - containedLazy.setContainingLazy( entity ); session.persist( containedLazy ); } } ); } + @AfterEach void cleanUp(SessionFactoryScope scope) { - scope.inTransaction( session -> { - List containedList = session.createQuery( - "select c from ContainedEntity c", ContainedEntity.class ).list(); - for ( ContainedEntity entity : containedList ) { - entity.setContainingLazy( null ); - } - } ); scope.inTransaction( session -> { session.createMutationQuery( "delete RootEntity" ).executeUpdate(); session.createMutationQuery( "delete ContainedEntity" ).executeUpdate(); @@ -73,37 +65,34 @@ void cleanUp(SessionFactoryScope scope) { @Test void includeAll_fetchGraph(SessionFactoryScope scope) { // Before HHH-16175 gets fixed, this leads to AssertionError in StandardEntityGraphTraversalStateImpl.traverse - testLoadingWithGraph(scope, RootEntity.GRAPH_INCLUDE_ALL, GraphSemantic.FETCH, - true, true ); + testLoadingWithGraph( scope, RootEntity.GRAPH_INCLUDE_ALL, GraphSemantic.FETCH, true, true ); } @Test void includeAll_loadGraph(SessionFactoryScope scope) { // Before HHH-16175 gets fixed, this leads to AssertionError in StandardEntityGraphTraversalStateImpl.traverse - testLoadingWithGraph(scope, RootEntity.GRAPH_INCLUDE_ALL, GraphSemantic.LOAD, - true, true ); + testLoadingWithGraph( scope, RootEntity.GRAPH_INCLUDE_ALL, GraphSemantic.LOAD, true, true ); } @Test void includeNone_fetchGraph(SessionFactoryScope scope) { - testLoadingWithGraph(scope, RootEntity.GRAPH_INCLUDE_NONE, GraphSemantic.FETCH, - false, false ); + testLoadingWithGraph( scope, RootEntity.GRAPH_INCLUDE_NONE, GraphSemantic.FETCH, false, false ); } @Test void includeNone_loadGraph(SessionFactoryScope scope) { - testLoadingWithGraph(scope, RootEntity.GRAPH_INCLUDE_NONE, GraphSemantic.LOAD, - true, false ); + testLoadingWithGraph( scope, RootEntity.GRAPH_INCLUDE_NONE, GraphSemantic.LOAD, true, false ); } - private void testLoadingWithGraph(SessionFactoryScope scope, String graphName, GraphSemantic graphSemantic, + private void testLoadingWithGraph( + SessionFactoryScope scope, String graphName, GraphSemantic graphSemantic, boolean expectContainedEagerInitialized, boolean expectContainedLazyInitialized) { scope.inTransaction( session -> { assertThat( session.createQuery( "select e from RootEntity e where id in (:ids)", RootEntity.class ) - .applyGraph( session.getEntityGraph( graphName ), graphSemantic ) - .setFetchSize( 100 ) - .setParameter( "ids", List.of( 0L, 100L, 200L ) ) - .list() ) + .applyGraph( session.getEntityGraph( graphName ), graphSemantic ) + .setFetchSize( 100 ) + .setParameter( "ids", List.of( 0L, 100L, 200L ) ) + .list() ) .isNotEmpty() .allSatisfy( loaded -> assertThat( Hibernate.isInitialized( loaded.getContainedEager() ) ) .as( "Hibernate::isInitialized for .getContainedEager() on %s", loaded ) @@ -133,7 +122,7 @@ static class RootEntity { @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST) private ContainedEntity containedEager; - @OneToMany(mappedBy = "containingLazy", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) private List containedLazy = new ArrayList<>(); public RootEntity() { @@ -177,10 +166,6 @@ static class ContainedEntity { @OneToOne(mappedBy = "containedEager") private RootEntity containingEager; - @ManyToOne - private RootEntity containingLazy; - - public ContainedEntity() { } @@ -204,12 +189,5 @@ public void setContainingEager(RootEntity containingEager) { this.containingEager = containingEager; } - public RootEntity getContainingLazy() { - return containingLazy; - } - - public void setContainingLazy(RootEntity containingLazy) { - this.containingLazy = containingLazy; - } } } From bce328cb291c086364e12e19e67dc3f218ed1741 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 23 Feb 2023 10:59:35 +0000 Subject: [PATCH 0111/1497] HHH-16221 Improve extensibility of CockroachDB and PostgreSQL dialects --- .../community/dialect/CockroachLegacyDialect.java | 7 +++++-- .../community/dialect/PostgreSQLLegacyDialect.java | 7 +++++-- .../java/org/hibernate/dialect/CockroachDialect.java | 7 +++++-- .../org/hibernate/dialect/PostgreSQLDialect.java | 12 ++++++++++-- 4 files changed, 25 insertions(+), 8 deletions(-) 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 5210cbe6614a..cb7f9a386e44 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 @@ -114,8 +114,8 @@ public class CockroachLegacyDialect extends Dialect { // Pre-compile and reuse pattern private static final Pattern CRDB_VERSION_PATTERN = Pattern.compile( "v[\\d]+(\\.[\\d]+)?(\\.[\\d]+)?" ); - private static final DatabaseVersion DEFAULT_VERSION = DatabaseVersion.make( 19, 2 ); - private final PostgreSQLDriverKind driverKind; + protected static final DatabaseVersion DEFAULT_VERSION = DatabaseVersion.make( 19, 2 ); + protected final PostgreSQLDriverKind driverKind; public CockroachLegacyDialect() { this( DEFAULT_VERSION ); @@ -322,7 +322,10 @@ protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration ty @Override public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { super.contributeTypes( typeContributions, serviceRegistry ); + contributeCockroachTypes( typeContributions, serviceRegistry ); + } + protected void contributeCockroachTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() .getJdbcTypeRegistry(); jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE ); 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 6bdc0bb54c80..ad6893077c1f 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 @@ -156,7 +156,7 @@ public class PostgreSQLLegacyDialect extends Dialect { private static final PostgreSQLIdentityColumnSupport IDENTITY_COLUMN_SUPPORT = new PostgreSQLIdentityColumnSupport(); - private final PostgreSQLDriverKind driverKind; + protected final PostgreSQLDriverKind driverKind; private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this); public PostgreSQLLegacyDialect() { @@ -1313,8 +1313,11 @@ public void augmentRecognizedTableTypes(List tableTypesList) { @Override public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { - super.contributeTypes(typeContributions, serviceRegistry); + super.contributeTypes( typeContributions, serviceRegistry ); + contributePostgreSQLTypes( typeContributions, serviceRegistry ); + } + protected void contributePostgreSQLTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() .getJdbcTypeRegistry(); // For discussion of BLOB support in Postgres, as of 8.4, have a peek at 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 ef0c74d91910..bff9f06d5e3a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachDialect.java @@ -122,9 +122,9 @@ public class CockroachDialect extends Dialect { // Pre-compile and reuse pattern private static final Pattern CRDB_VERSION_PATTERN = Pattern.compile( "v[\\d]+(\\.[\\d]+)?(\\.[\\d]+)?" ); - private static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 21, 1 ); + protected static final DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 21, 1 ); - private final PostgreSQLDriverKind driverKind; + protected final PostgreSQLDriverKind driverKind; public CockroachDialect() { this( MINIMUM_VERSION ); @@ -333,7 +333,10 @@ protected Integer resolveSqlTypeCode(String columnTypeName, TypeConfiguration ty @Override public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { super.contributeTypes( typeContributions, serviceRegistry ); + contributeCockroachTypes( typeContributions, serviceRegistry ); + } + protected void contributeCockroachTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() .getJdbcTypeRegistry(); jdbcTypeRegistry.addDescriptor( TIMESTAMP_UTC, InstantAsTimestampWithTimeZoneJdbcType.INSTANCE ); 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 e01012313d64..84ebef4ab49b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLDialect.java @@ -136,12 +136,12 @@ * @author Gavin King */ public class PostgreSQLDialect extends Dialect { - private final static DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 10 ); + protected final static DatabaseVersion MINIMUM_VERSION = DatabaseVersion.make( 10 ); private static final PostgreSQLIdentityColumnSupport IDENTITY_COLUMN_SUPPORT = new PostgreSQLIdentityColumnSupport(); private final UniqueDelegate uniqueDelegate = new CreateTableUniqueDelegate(this); - private final PostgreSQLDriverKind driverKind; + protected final PostgreSQLDriverKind driverKind; private final OptionalTableUpdateStrategy optionalTableUpdateStrategy; public PostgreSQLDialect() { @@ -1308,7 +1308,15 @@ public void augmentRecognizedTableTypes(List tableTypesList) { @Override public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { super.contributeTypes(typeContributions, serviceRegistry); + contributePostgreSQLTypes(typeContributions, serviceRegistry); + } + /** + * Allow for extension points to override this only + * @param typeContributions + * @param serviceRegistry + */ + protected void contributePostgreSQLTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) { final JdbcTypeRegistry jdbcTypeRegistry = typeContributions.getTypeConfiguration() .getJdbcTypeRegistry(); // For discussion of BLOB support in Postgres, as of 8.4, have a peek at From 02da5a81a8de3c7ce6152681ebe5fe3d5c6dac92 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Thu, 23 Feb 2023 12:20:21 +0000 Subject: [PATCH 0112/1497] HHH-16222 Improve warning triggered when the PostgreSQL JDBC driver is not accessible --- .../org/hibernate/dialect/PostgreSQLPGObjectJdbcType.java | 2 +- .../main/java/org/hibernate/internal/CoreMessageLogger.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLPGObjectJdbcType.java b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLPGObjectJdbcType.java index b52be17bc027..20f5747f53fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLPGObjectJdbcType.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/PostgreSQLPGObjectJdbcType.java @@ -52,7 +52,7 @@ public abstract class PostgreSQLPGObjectJdbcType implements JdbcType { valueSetter = ReflectHelper.setterMethodOrNull( pgObjectClass, "value", String.class ); } catch (Exception e) { - LOG.warn( "PostgreSQL JDBC driver classes are inaccessible and thus, certain DDL types like JSONB, JSON, GEOMETRY can not be used!", e ); + LOG.postgreSQLJdbcDriverNotAccessible(); } PG_OBJECT_CONSTRUCTOR = constructor; TYPE_SETTER = typeSetter; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 2b65a8c46097..d110ca8522a2 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1819,4 +1819,9 @@ void attemptToAssociateProxyWithTwoOpenSessions( id = 513) void unableToGenerateReflectionOptimizer(String className, @Cause Throwable cause); + @LogMessage(level = WARN) + @Message(value = "PostgreSQL JDBC driver classes are inaccessible and thus, certain DDL types like JSONB, JSON, GEOMETRY can not be used.", + id = 514) + void postgreSQLJdbcDriverNotAccessible(); + } From 88ed4fdb91027f74e5f414e94c6a0df22a165e5e Mon Sep 17 00:00:00 2001 From: Jan Schatteman Date: Wed, 22 Feb 2023 21:09:18 +0100 Subject: [PATCH 0113/1497] Fix broken doc link in native.adoc Signed-off-by: Jan Schatteman --- .../main/asciidoc/userguide/chapters/query/native/Native.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc b/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc index a9330e76c8e0..2938c377e529 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc @@ -494,7 +494,7 @@ include::{example-dir-model}/PersonNames.java[tags=sql-ConstructorResult-dto-exa ==== [source, JAVA, indent=0] ---- -include::{modeldir}/Person.java[tags=sql-multiple-scalar-values-dto-NamedNativeQuery-example] +include::{example-dir-model}/Person.java[tags=sql-multiple-scalar-values-dto-NamedNativeQuery-example] ---- ==== From a36f6aa73679c09cff7bb65990b293e312985aad Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 23 Feb 2023 17:33:34 -0600 Subject: [PATCH 0114/1497] HHH-16226 - Introduce JdbcValuesMappingProducerProvider --- .../NamedProcedureCallDefinitionImpl.java | 11 +++- .../procedure/internal/ProcedureCallImpl.java | 14 ++--- .../query/results/ResultSetMapping.java | 58 ++++++++++++++++++- .../query/results/ResultSetMappingImpl.java | 14 +++-- .../query/sql/internal/NativeQueryImpl.java | 30 ++++++---- .../internal/ResultSetMappingProcessor.java | 12 ++-- .../service/StandardServiceInitiators.java | 3 + .../sql/ast/spi/AbstractSqlAstTranslator.java | 14 +++-- ...aluesMappingProducerProviderInitiator.java | 38 ++++++++++++ ...ValuesMappingProducerProviderStandard.java | 44 ++++++++++++++ .../JdbcValuesMappingProducerProvider.java | 32 ++++++++++ ...dbcValuesMappingProducerProviderTests.java | 54 +++++++++++++++++ 12 files changed, 288 insertions(+), 36 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerProviderInitiator.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesMappingProducerProviderStandard.java create mode 100644 hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesMappingProducerProvider.java create mode 100644 hibernate-core/src/test/java/org/hibernate/orm/test/intg/reactive/JdbcValuesMappingProducerProviderTests.java diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedProcedureCallDefinitionImpl.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedProcedureCallDefinitionImpl.java index 76129a629dad..a4eae0ac0b57 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedProcedureCallDefinitionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/NamedProcedureCallDefinitionImpl.java @@ -23,7 +23,9 @@ import org.hibernate.procedure.internal.Util; import org.hibernate.procedure.spi.NamedCallableQueryMemento; import org.hibernate.procedure.spi.ParameterStrategy; +import org.hibernate.query.results.ResultSetMapping; import org.hibernate.query.results.ResultSetMappingImpl; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducerProvider; import jakarta.persistence.NamedStoredProcedureQuery; import jakarta.persistence.ParameterMode; @@ -86,7 +88,7 @@ public NamedCallableQueryMemento resolve(SessionFactoryImplementor sessionFactor final boolean specifiesResultClasses = resultClasses != null && resultClasses.length > 0; final boolean specifiesResultSetMappings = resultSetMappings != null && resultSetMappings.length > 0; - ResultSetMappingImpl resultSetMapping = new ResultSetMappingImpl( registeredName ); + final ResultSetMapping resultSetMapping = buildResultSetMapping( registeredName, sessionFactory ); if ( specifiesResultClasses ) { Util.resolveResultSetMappingClasses( @@ -125,6 +127,13 @@ else if ( specifiesResultSetMappings ) { ); } + private ResultSetMapping buildResultSetMapping(String registeredName, SessionFactoryImplementor sessionFactory) { + return sessionFactory + .getServiceRegistry() + .getService( JdbcValuesMappingProducerProvider.class ) + .buildResultSetMapping( registeredName, false, sessionFactory ); + } + static class ParameterDefinitions { private final ParameterStrategy parameterStrategy; private final ParameterDefinition[] parameterDefinitions; diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index 58c31aa96cc3..035888e99db4 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -51,7 +51,6 @@ import org.hibernate.query.internal.QueryOptionsImpl; import org.hibernate.query.procedure.ProcedureParameter; import org.hibernate.query.results.ResultSetMapping; -import org.hibernate.query.results.ResultSetMappingImpl; import org.hibernate.query.spi.AbstractQuery; import org.hibernate.query.spi.MutableQueryOptions; import org.hibernate.query.spi.QueryImplementor; @@ -94,6 +93,7 @@ import static org.hibernate.jpa.HibernateHints.HINT_CALLABLE_FUNCTION; import static org.hibernate.procedure.internal.NamedCallableQueryMementoImpl.ParameterMementoImpl.fromRegistration; +import static org.hibernate.query.results.ResultSetMapping.resolveResultSetMapping; /** * Standard implementation of {@link ProcedureCall} @@ -134,7 +134,7 @@ public ProcedureCallImpl(SharedSessionContractImplementor session, String proced this.parameterMetadata = new ProcedureParameterMetadataImpl(); this.paramBindings = new ProcedureParamBindings( parameterMetadata, getSessionFactory() ); - this.resultSetMapping = new ResultSetMappingImpl( procedureName, true ); + this.resultSetMapping = resolveResultSetMapping( procedureName, true, session.getSessionFactory() ); this.synchronizedQuerySpaces = null; } @@ -160,7 +160,7 @@ public ProcedureCallImpl(SharedSessionContractImplementor session, String proced final String mappingId = procedureName + ":" + StringHelper.join( ",", resultClasses ); - this.resultSetMapping = new ResultSetMappingImpl( mappingId ); + this.resultSetMapping = ResultSetMapping.resolveResultSetMapping( mappingId, session.getSessionFactory() ); Util.resolveResultSetMappingClasses( resultClasses, @@ -193,7 +193,7 @@ public ProcedureCallImpl( this.synchronizedQuerySpaces = new HashSet<>(); final String mappingId = procedureName + ":" + StringHelper.join( ",", resultSetMappingNames ); - this.resultSetMapping = new ResultSetMappingImpl( mappingId ); + this.resultSetMapping = ResultSetMapping.resolveResultSetMapping( mappingId, session.getSessionFactory() ); Util.resolveResultSetMappingNames( resultSetMappingNames, @@ -219,7 +219,7 @@ public ProcedureCallImpl( this.synchronizedQuerySpaces = CollectionHelper.makeCopy( memento.getQuerySpaces() ); - this.resultSetMapping = new ResultSetMappingImpl( memento.getRegistrationName() ); + this.resultSetMapping = ResultSetMapping.resolveResultSetMapping( memento.getRegistrationName(), session.getSessionFactory() ); Util.resolveResultSetMappings( memento.getResultSetMappingNames(), @@ -252,7 +252,7 @@ public ProcedureCallImpl( this.synchronizedQuerySpaces = CollectionHelper.makeCopy( memento.getQuerySpaces() ); final String mappingId = procedureName + ":" + StringHelper.join( ",", resultTypes ); - this.resultSetMapping = new ResultSetMappingImpl( mappingId ); + this.resultSetMapping = ResultSetMapping.resolveResultSetMapping( mappingId, session.getSessionFactory() ); Util.resolveResultSetMappings( null, @@ -279,7 +279,7 @@ public ProcedureCallImpl( this.synchronizedQuerySpaces = CollectionHelper.makeCopy( memento.getQuerySpaces() ); final String mappingId = procedureName + ":" + StringHelper.join( ",", resultSetMappingNames ); - this.resultSetMapping = new ResultSetMappingImpl( mappingId ); + this.resultSetMapping = ResultSetMapping.resolveResultSetMapping( mappingId, session.getSessionFactory() ); Util.resolveResultSetMappings( resultSetMappingNames, diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMapping.java b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMapping.java index 5ff2f0102149..75518467481b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMapping.java @@ -6,21 +6,22 @@ */ package org.hibernate.query.results; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.hibernate.Incubating; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.NativeQuery; import org.hibernate.query.named.NamedResultSetMappingMemento; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducerProvider; /** * Acts as the {@link JdbcValuesMappingProducer} for {@link NativeQuery} * or {@link org.hibernate.procedure.ProcedureCall} / {@link jakarta.persistence.StoredProcedureQuery} - * instances. - * - * Can be defined