From b6683d235218e62130563ccf60b58a9a83642448 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Fri, 6 Aug 2021 13:26:54 +0200 Subject: [PATCH] Fix some more result set mapping issues and fix HHH-7525 as well as HHH-10504 --- .../chapters/query/native/Native.adoc | 9 +- .../org/hibernate/userguide/sql/SQLTest.java | 107 ++++++++---------- .../AbstractSharedSessionContract.java | 3 +- .../hibernate/loader/ast/spi/Loadable.java | 12 ++ .../metamodel/mapping/EntityMappingType.java | 21 ++++ .../entity/AbstractEntityPersister.java | 7 +- .../entity/SingleTableEntityPersister.java | 5 +- .../entity/UnionSubclassEntityPersister.java | 5 +- .../query/results/ResultSetMappingImpl.java | 90 +++++++++++++++ ...mpleteResultBuilderCollectionStandard.java | 2 +- .../CompleteResultBuilderEntityJpa.java | 2 +- .../CompleteResultBuilderEntityStandard.java | 2 +- .../dynamic/DynamicFetchBuilderLegacy.java | 81 +++++++------ .../DynamicResultBuilderEntityCalculated.java | 19 ++-- .../implicit/ImplicitFetchBuilderBasic.java | 15 ++- .../ImplicitModelPartResultBuilderEntity.java | 3 +- .../internal/ResultSetMappingProcessor.java | 2 +- .../MultiTableSqmMutationConverter.java | 5 +- .../RestrictedDeleteExecutionDelegate.java | 2 +- .../ast/tree/from/RootTableGroupProducer.java | 9 ++ .../internal/AbstractResultSetAccess.java | 2 +- .../formula/FormulaNativeQueryTest.java | 79 +++++++++++-- .../sql/autodiscovery/AutoDiscoveryTest.java | 42 +++---- 23 files changed, 357 insertions(+), 167 deletions(-) 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 657f7e6cd0..b80324f96a 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/query/native/Native.adoc @@ -182,8 +182,8 @@ include::{extrasdir}/sql-hibernate-entity-associations-query-many-to-one-join-ex As seen in the associated SQL query, Hibernate manages to construct the entity hierarchy without requiring any extra database roundtrip. ==== -By default, when using the `addJoin()` method, the result set will contain both entities that are joined. -To construct the entity hierarchy, you need to use a `ROOT_ENTITY` or `DISTINCT_ROOT_ENTITY` `ResultTransformer`. +Even when using the `addJoin()` method, the result list will only contain the root entity. +Joined entities will only be present for their respective association. [[sql-hibernate-entity-associations-query-many-to-one-join-result-transformer-example]] .Hibernate native query selecting entities with joined many-to-one association and `ResultTransformer` @@ -194,11 +194,6 @@ include::{sourcedir}/SQLTest.java[tags=sql-hibernate-entity-associations-query-m ---- ==== -[NOTE] -==== -Because of the `ROOT_ENTITY` `ResultTransformer`, the query above will return the parent-side as root entities. -==== - Notice that you added an alias name _pr_ to be able to specify the target property path of the join. It is possible to do the same eager joining for collections (e.g. the `Phone#calls` `one-to-many` association). diff --git a/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java b/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java index b34f50049b..2c39857e11 100644 --- a/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java +++ b/documentation/src/test/java/org/hibernate/userguide/sql/SQLTest.java @@ -15,7 +15,7 @@ import javax.persistence.PersistenceException; import org.hibernate.Session; import org.hibernate.dialect.H2Dialect; -import org.hibernate.dialect.Oracle8iDialect; +import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.loader.NonUniqueDiscoveredSqlAliasException; @@ -36,6 +36,7 @@ import org.hibernate.userguide.model.PhoneType; import org.hibernate.userguide.model.WireTransferPayment; import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.TestForIssue; import org.junit.Before; import org.junit.Test; @@ -335,17 +336,15 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase { @Test public void test_sql_jpa_entity_associations_query_many_to_one_join_example() { doInJPA( this::entityManagerFactory, entityManager -> { - //tag::sql-jpa-entity-associations-query-many-to-one-join-example[] List phones = entityManager.createNativeQuery( - "SELECT * " + + "SELECT ph.* " + "FROM Phone ph " + "JOIN Person pr ON ph.person_id = pr.id", Phone.class ) .getResultList(); - for(Phone phone : phones) { + for( Phone phone : phones ) { assertNotNull( phone.getPerson().getName() ); } - //end::sql-jpa-entity-associations-query-many-to-one-join-example[] assertEquals(3, phones.size()); }); } @@ -355,18 +354,16 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-entity-associations-query-many-to-one-join-example[] - List tuples = session.createNativeQuery( - "SELECT * " + + List tuples = session.createNativeQuery( + "SELECT {ph.*}, {pr.*} " + "FROM Phone ph " + "JOIN Person pr ON ph.person_id = pr.id" ) - .addEntity("phone", Phone.class ) - .addJoin( "pr", "phone.person") + .addEntity("ph", Phone.class ) + .addJoin( "pr", "ph.person" ) .list(); - for(Object[] tuple : tuples) { - Phone phone = (Phone) tuple[0]; - Person person = (Person) tuple[1]; - assertNotNull( person.getName() ); + for ( Phone phone : tuples ) { + assertNotNull( phone.getPerson().getName() ); } //end::sql-hibernate-entity-associations-query-many-to-one-join-example[] assertEquals(3, tuples.size()); @@ -378,37 +375,36 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-entity-associations-query-many-to-one-join-result-transformer-example[] - List persons = session.createNativeQuery( - "SELECT * " + + List phones = session.createNativeQuery( + "SELECT {ph.*}, {pr.*} " + "FROM Phone ph " + "JOIN Person pr ON ph.person_id = pr.id" ) - .addEntity("phone", Phone.class ) - .addJoin( "pr", "phone.person") - .setResultTransformer( RootEntityResultTransformer.INSTANCE ) + .addEntity("ph", Phone.class ) + .addJoin( "pr", "ph.person") .list(); - for(Person person : persons) { - person.getPhones(); + for ( Phone person : phones ) { + person.getPerson(); } //end::sql-hibernate-entity-associations-query-many-to-one-join-result-transformer-example[] - assertEquals(3, persons.size()); + assertEquals(3, phones.size()); }); } @Test @RequiresDialect(H2Dialect.class) - @RequiresDialect(Oracle8iDialect.class) + @RequiresDialect(OracleDialect.class) @RequiresDialect(PostgreSQLDialect.class) public void test_sql_jpa_entity_associations_query_one_to_many_join_example() { doInJPA( this::entityManagerFactory, entityManager -> { //tag::sql-jpa-entity-associations-query-one-to-many-join-example[] List phones = entityManager.createNativeQuery( - "SELECT * " + + "SELECT ph.* " + "FROM Phone ph " + "JOIN phone_call c ON c.phone_id = ph.id", Phone.class ) .getResultList(); - for(Phone phone : phones) { + for ( Phone phone : phones ) { List calls = phone.getCalls(); } //end::sql-jpa-entity-associations-query-one-to-many-join-example[] @@ -417,50 +413,43 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase { } @Test + @TestForIssue( jiraKey = "HHH-10504") public void test_sql_hibernate_entity_associations_query_one_to_many_join_example_1() { - try { - doInJPA( this::entityManagerFactory, entityManager -> { - Session session = entityManager.unwrap( Session.class ); - List phones = session.createNativeQuery( - "SELECT * " + - "FROM Phone ph " + - "JOIN phone_call c ON c.phone_id = ph.id" ) - .addEntity("phone", Phone.class ) - .addJoin( "c", "phone.calls") - .setResultTransformer( DistinctRootEntityResultTransformer.INSTANCE) - .list(); + doInJPA( this::entityManagerFactory, entityManager -> { + Session session = entityManager.unwrap( Session.class ); + List phones = session.createNativeQuery( + "SELECT {ph.*}, {c.*} " + + "FROM Phone ph " + + "JOIN phone_call c ON c.phone_id = ph.id" ) + .addEntity("ph", Phone.class ) + .addJoin( "c", "ph.calls" ) + .list(); - for(Phone phone : phones) { - List calls = phone.getCalls(); - } - assertEquals(2, phones.size()); - }); - } - catch (Exception e) { - log.error( "HHH-10504", e ); - //See issue https://hibernate.atlassian.net/browse/HHH-10504 - } + for ( Phone phone : phones ) { + List calls = phone.getCalls(); + } + assertEquals( 2, phones.size() ); + }); } @Test @RequiresDialect(H2Dialect.class) - @RequiresDialect(Oracle8iDialect.class) + @RequiresDialect(OracleDialect.class) @RequiresDialect(PostgreSQLDialect.class) public void test_sql_hibernate_entity_associations_query_one_to_many_join_example_2() { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-entity-associations-query-one-to-many-join-example[] - List tuples = session.createNativeQuery( - "SELECT * " + + List tuples = session.createNativeQuery( + "SELECT {ph.*}, {c.*} " + "FROM Phone ph " + "JOIN phone_call c ON c.phone_id = ph.id" ) - .addEntity("phone", Phone.class ) - .addJoin( "c", "phone.calls") + .addEntity("ph", Phone.class ) + .addJoin( "c", "ph.calls") .list(); - for(Object[] tuple : tuples) { - Phone phone = (Phone) tuple[0]; - Call call = (Call) tuple[1]; + for ( Phone phone : tuples ) { + List calls = phone.getCalls(); } //end::sql-hibernate-entity-associations-query-one-to-many-join-example[] assertEquals(2, tuples.size()); @@ -472,10 +461,10 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase { try { doInJPA( this::entityManagerFactory, entityManager -> { //tag::sql-jpa-multi-entity-query-example[] - List entities = entityManager.createNativeQuery( + List entities = entityManager.createNativeQuery( "SELECT * " + "FROM Person pr, Partner pt " + - "WHERE pr.name = pt.name" ) + "WHERE pr.name = pt.name", Person.class ) .getResultList(); //end::sql-jpa-multi-entity-query-example[] assertEquals(2, entities.size()); @@ -493,10 +482,10 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase { doInJPA( this::entityManagerFactory, entityManager -> { Session session = entityManager.unwrap( Session.class ); //tag::sql-hibernate-multi-entity-query-example[] - List entities = session.createNativeQuery( + List entities = session.createNativeQuery( "SELECT * " + "FROM Person pr, Partner pt " + - "WHERE pr.name = pt.name" ) + "WHERE pr.name = pt.name", Person.class ) .list(); //end::sql-hibernate-multi-entity-query-example[] assertEquals( 2, entities.size() ); @@ -720,7 +709,7 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase { @Test @RequiresDialect(H2Dialect.class) - @RequiresDialect(Oracle8iDialect.class) + @RequiresDialect(OracleDialect.class) @RequiresDialect(PostgreSQLDialect.class) public void test_sql_jpa_entity_associations_named_query_example() { doInJPA( this::entityManagerFactory, entityManager -> { @@ -741,7 +730,7 @@ public class SQLTest extends BaseEntityManagerFunctionalTestCase { @Test @RequiresDialect(H2Dialect.class) - @RequiresDialect(Oracle8iDialect.class) + @RequiresDialect(OracleDialect.class) @RequiresDialect(PostgreSQLDialect.class) public void test_sql_hibernate_entity_associations_named_query_example() { doInJPA( this::entityManagerFactory, entityManager -> { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index bdd52a3489..76f449aa89 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -26,6 +26,7 @@ import org.hibernate.FlushMode; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.Interceptor; +import org.hibernate.LockMode; import org.hibernate.MultiTenancyStrategy; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.SessionEventListener; @@ -705,7 +706,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont try { NativeQueryImplementor query = createNativeQuery( sqlString ); -// query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); + query.addEntity( "alias1", resultClass.getName(), LockMode.READ ); return query; } catch (RuntimeException he) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/Loadable.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/Loadable.java index ef1a6403fc..853361fd5c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/Loadable.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/Loadable.java @@ -13,6 +13,7 @@ import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.spi.SqlAliasBase; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.tree.from.RootTableGroupProducer; @@ -44,4 +45,15 @@ public interface Loadable extends ModelPart, RootTableGroupProducer { SqlAstCreationState creationState, SqlAstCreationContext creationContext) { throw new NotYetImplementedFor6Exception( getClass() ); } + + @Override + default TableGroup createRootTableGroup( + boolean canUseInnerJoins, + NavigablePath navigablePath, + String explicitSourceAlias, + Supplier> additionalPredicateCollectorAccess, + SqlAliasBase sqlAliasBase, + SqlAstCreationState creationState, SqlAstCreationContext creationContext) { + throw new NotYetImplementedFor6Exception( getClass() ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java index f2b9f4fff7..fd3329ab83 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java @@ -282,11 +282,32 @@ public interface EntityMappingType extends ManagedMappingType, EntityValuedModel Supplier> additionalPredicateCollectorAccess, SqlAstCreationState creationState, SqlAstCreationContext creationContext) { + return createRootTableGroup( + canUseInnerJoins, + navigablePath, + explicitSourceAlias, + additionalPredicateCollectorAccess, + creationState.getSqlAliasBaseGenerator().createSqlAliasBase( getSqlAliasStem() ), + creationState, + creationContext + ); + } + + @Override + default TableGroup createRootTableGroup( + boolean canUseInnerJoins, + NavigablePath navigablePath, + String explicitSourceAlias, + Supplier> additionalPredicateCollectorAccess, + SqlAliasBase sqlAliasBase, + SqlAstCreationState creationState, + SqlAstCreationContext creationContext) { return getEntityPersister().createRootTableGroup( canUseInnerJoins, navigablePath, explicitSourceAlias, additionalPredicateCollectorAccess, + sqlAliasBase, creationState, creationContext ); 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 904d08a2d6..ccb085d785 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 @@ -1372,11 +1372,10 @@ public abstract class AbstractEntityPersister NavigablePath navigablePath, String explicitSourceAlias, Supplier> additionalPredicateCollectorAccess, + SqlAliasBase sqlAliasBase, SqlAstCreationState creationState, SqlAstCreationContext creationContext) { final SqlExpressionResolver sqlExpressionResolver = creationState.getSqlExpressionResolver(); - final SqlAliasBaseGenerator aliasBaseGenerator = creationState.getSqlAliasBaseGenerator(); - final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() ); final TableReference primaryTableReference = createPrimaryTableReference( sqlAliasBase, @@ -1411,7 +1410,9 @@ public abstract class AbstractEntityPersister Collections.emptySet() ), joinedTableReference, - generateJoinPredicate( + additionalPredicateCollectorAccess == null + ? null + : generateJoinPredicate( primaryTableReference, joinedTableReference, getSubclassTableKeyColumns( i ), diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index cebd06fcbc..aac232c613 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -47,6 +47,7 @@ import org.hibernate.query.NavigablePath; import org.hibernate.sql.InFragment; import org.hibernate.sql.Insert; import org.hibernate.sql.SelectFragment; +import org.hibernate.sql.ast.spi.SqlAliasBase; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; @@ -931,6 +932,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { NavigablePath navigablePath, String explicitSourceAlias, Supplier> additionalPredicateCollectorAccess, + SqlAliasBase sqlAliasBase, SqlAstCreationState creationState, SqlAstCreationContext creationContext) { final TableGroup tableGroup = super.createRootTableGroup( @@ -938,11 +940,12 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { navigablePath, explicitSourceAlias, additionalPredicateCollectorAccess, + sqlAliasBase, creationState, creationContext ); - if ( needsDiscriminator() ) { + if ( additionalPredicateCollectorAccess != null && needsDiscriminator() ) { final Predicate discriminatorPredicate = createDiscriminatorPredicate( tableGroup, creationState.getSqlExpressionResolver(), 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 fe29d1e443..3daeeca8cc 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 @@ -245,11 +245,10 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister { NavigablePath navigablePath, String explicitSourceAlias, Supplier> additionalPredicateCollectorAccess, + SqlAliasBase sqlAliasBase, SqlAstCreationState creationState, SqlAstCreationContext creationContext) { - final SqlAliasBase sqlAliasBase = creationState.getSqlAliasBaseGenerator().createSqlAliasBase( getSqlAliasStem() ); - - final TableReference tableReference = resolvePrimaryTableReference(sqlAliasBase); + final TableReference tableReference = resolvePrimaryTableReference( sqlAliasBase ); return new UnionTableGroup( canUseInnerJoins, navigablePath, tableReference, this, explicitSourceAlias ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java index 6289c69e6f..54e7ec58c0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java @@ -12,6 +12,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -19,15 +20,22 @@ import org.hibernate.Incubating; import org.hibernate.Internal; import org.hibernate.LockMode; import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.util.StringHelper; +import org.hibernate.loader.NonUniqueDiscoveredSqlAliasException; import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.named.NamedResultSetMappingMemento; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.basic.BasicResult; +import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; +import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; @@ -183,10 +191,92 @@ public class ResultSetMappingImpl implements ResultSetMapping { domainResults.add( domainResult ); } + // We only need this check when we actually have result builders + // As people should be able to just run native queries and work with tuples + if ( resultBuilders != null ) { + final Set knownDuplicateAliases = new TreeSet<>( String.CASE_INSENSITIVE_ORDER ); + if ( resultBuilders.size() == 1 && domainResults.size() == 1 && domainResults.get( 0 ) instanceof EntityResult ) { + // Special case for result set mappings that just fetch a single polymorphic entity + final EntityResult entityResult = (EntityResult) domainResults.get( 0 ); + final boolean polymorphic = entityResult.getReferencedMappingContainer() + .getEntityPersister() + .getEntityMetamodel() + .isPolymorphic(); + // We only need to check for duplicate aliases if we have join fetches, + // otherwise we assume that even if there are duplicate aliases, the values are equivalent. + // If we don't do that, there is no way to fetch joined inheritance entities + if ( polymorphic && ( legacyFetchBuilders == null || legacyFetchBuilders.isEmpty() ) + && !hasJoinFetches( entityResult.getFetches() ) ) { + final Set aliases = new TreeSet<>( String.CASE_INSENSITIVE_ORDER ); + final AbstractEntityPersister entityPersister = (AbstractEntityPersister) entityResult.getReferencedMappingContainer() + .getEntityPersister(); + for ( String[] columns : entityPersister.getContraintOrderedTableKeyColumnClosure() ) { + addColumns( aliases, knownDuplicateAliases, columns ); + } + addColumn( aliases, knownDuplicateAliases, entityPersister.getDiscriminatorColumnName() ); + addColumn( aliases, knownDuplicateAliases, entityPersister.getVersionColumnName() ); + for ( int i = 0; i < entityPersister.countSubclassProperties(); i++ ) { + addColumns( + aliases, + knownDuplicateAliases, + entityPersister.getSubclassPropertyColumnNames( i ) + ); + } + } + } + final String[] aliases = new String[rowSize]; + final Map aliasHasDuplicates = new HashMap<>( rowSize ); + for ( int i = 0; i < rowSize; i++ ) { + aliasHasDuplicates.compute( + aliases[i] = jdbcResultsMetadata.resolveColumnName( i + 1 ), + (k, v) -> v == null ? Boolean.FALSE : Boolean.TRUE + ); + } + // Only check for duplicates for the selections that we actually use + for ( SqlSelection sqlSelection : sqlSelections ) { + final String alias = aliases[sqlSelection.getValuesArrayPosition()]; + if ( !knownDuplicateAliases.contains( alias ) && aliasHasDuplicates.get( alias ) == Boolean.TRUE ) { + throw new NonUniqueDiscoveredSqlAliasException( + "Encountered a duplicated sql alias [" + alias + "] during auto-discovery of a native-sql query" + ); + } + } + } final Map registeredLockModes = creationState.getRegisteredLockModes(); return new JdbcValuesMappingImpl( sqlSelections, domainResults, rowSize, registeredLockModes ); } + private static void addColumns(Set aliases, Set knownDuplicateAliases, String[] columns) { + for ( int i = 0; i < columns.length; i++ ) { + addColumn( aliases, knownDuplicateAliases, columns[i] ); + } + } + + private static void addColumn(Set aliases, Set knownDuplicateAliases, String column) { + if ( column != null && !aliases.add( column ) ) { + knownDuplicateAliases.add( column ); + } + } + + private static boolean hasJoinFetches(List fetches) { + for ( int i = 0; i < fetches.size(); i++ ) { + final Fetch fetch = fetches.get( i ); + if ( fetch instanceof BasicFetch || fetch.getTiming() == FetchTiming.DELAYED ) { + // That's fine + } + else if ( fetch instanceof EmbeddableResultGraphNode ) { + // Check all these fetches as well + if ( hasJoinFetches( ( (EmbeddableResultGraphNode) fetch ).getFetches() ) ) { + return true; + } + } + else { + return true; + } + } + return false; + } + private DomainResult makeImplicitDomainResult( int valuesArrayPosition, Consumer sqlSelectionConsumer, diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderCollectionStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderCollectionStandard.java index 741a9bca69..bd6dac33aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderCollectionStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderCollectionStandard.java @@ -92,7 +92,7 @@ public class CompleteResultBuilderCollectionStandard implements CompleteResultBu false, navigablePath, tableAlias, - () -> predicate -> {}, + null, creationStateImpl, sessionFactory ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java index 6d1bc736f3..2eb8b4f50c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java @@ -84,7 +84,7 @@ public class CompleteResultBuilderEntityJpa implements CompleteResultBuilderEnti true, navigablePath, null, - () -> predicate -> {}, + null, impl.getSqlAstCreationState(), impl.getSqlAstCreationState().getCreationContext() ) diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityStandard.java index dba602a1f1..341872b742 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityStandard.java @@ -120,7 +120,7 @@ public class CompleteResultBuilderEntityStandard implements CompleteResultBuilde true, navigablePath, null, - () -> predicate -> {}, + null, impl, impl.getCreationContext() ) diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java index 43e3b541ff..fb0fc41346 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java @@ -12,6 +12,7 @@ import java.util.function.BiConsumer; import java.util.function.BiFunction; import org.hibernate.LockMode; +import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.AttributeMapping; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; @@ -127,43 +128,55 @@ public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue tableGroup = ownerTableGroup; } - final ForeignKeyDescriptor keyDescriptor; - if ( attributeMapping instanceof PluralAttributeMapping ) { - final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) attributeMapping; - keyDescriptor = pluralAttributeMapping.getKeyDescriptor(); + if ( columnNames != null ) { + final ForeignKeyDescriptor keyDescriptor; + if ( attributeMapping instanceof PluralAttributeMapping ) { + final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) attributeMapping; + keyDescriptor = pluralAttributeMapping.getKeyDescriptor(); + } + else { + // Not sure if this fetch builder can also be used with other attribute mappings + assert attributeMapping instanceof ToOneAttributeMapping; + + final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping; + keyDescriptor = toOneAttributeMapping.getForeignKeyDescriptor(); + } + + keyDescriptor.forEachSelectable( + (selectionIndex, selectableMapping) -> { + resolveSqlSelection( + columnNames.get( selectionIndex ), + createColumnReferenceKey( + tableGroup.getTableReference( selectableMapping.getContainingTableExpression() ), + selectableMapping.getSelectionExpression() + ), + selectableMapping.getJdbcMapping(), + jdbcResultsMetadata, + domainResultCreationState + ); + } + ); + + // We process the fetch builder such that it contains a resultBuilderEntity before calling this method in ResultSetMappingProcessor + assert resultBuilderEntity != null; + + return resultBuilderEntity.buildFetch( + parent, + attributeMapping, + jdbcResultsMetadata, + creationState + ); } else { - // Not sure if this fetch builder can also be used with other attribute mappings - assert attributeMapping instanceof ToOneAttributeMapping; - - final ToOneAttributeMapping toOneAttributeMapping = (ToOneAttributeMapping) attributeMapping; - keyDescriptor = toOneAttributeMapping.getForeignKeyDescriptor(); + return parent.generateFetchableFetch( + attributeMapping, + fetchPath, + FetchTiming.IMMEDIATE, + true, + null, + domainResultCreationState + ); } - - keyDescriptor.forEachSelectable( - (selectionIndex, selectableMapping) -> { - resolveSqlSelection( - columnNames.get( selectionIndex ), - createColumnReferenceKey( - tableGroup.getTableReference( selectableMapping.getContainingTableExpression() ), - selectableMapping.getSelectionExpression() - ), - selectableMapping.getJdbcMapping(), - jdbcResultsMetadata, - domainResultCreationState - ); - } - ); - - // We process the fetch builder such that it contains a resultBuilderEntity before calling this method in ResultSetMappingProcessor - assert resultBuilderEntity != null; - - return resultBuilderEntity.buildFetch( - parent, - attributeMapping, - jdbcResultsMetadata, - creationState - ); } private void resolveSqlSelection( diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java index 0671d99e9e..a4315b4677 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java @@ -15,9 +15,8 @@ import org.hibernate.query.NativeQuery; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.ResultsHelper; -import org.hibernate.query.results.TableGroupImpl; import org.hibernate.sql.ast.spi.SqlAliasBaseConstant; -import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; @@ -103,18 +102,14 @@ public class DynamicResultBuilderEntityCalculated implements DynamicResultBuilde DomainResultCreationState domainResultCreationState) { final DomainResultCreationStateImpl creationStateImpl = ResultsHelper.impl( domainResultCreationState ); - final TableReference tableReference = entityMapping.createPrimaryTableReference( - new SqlAliasBaseConstant( tableAlias ), - creationStateImpl.getSqlExpressionResolver(), - creationStateImpl.getCreationContext() - ); - - final TableGroupImpl tableGroup = new TableGroupImpl( + final TableGroup tableGroup = entityMapping.createRootTableGroup( + true, navigablePath, tableAlias, - tableReference, - entityMapping, - tableAlias + null, + new SqlAliasBaseConstant( tableAlias ), + creationStateImpl, + creationStateImpl.getCreationContext() ); creationStateImpl.getFromClauseAccess().registerTableGroup( navigablePath, tableGroup ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java index c92fde8216..fba0740fd3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java @@ -53,11 +53,22 @@ public class ImplicitFetchBuilderBasic implements ImplicitFetchBuilder { .getFromClauseAccess() .getTableGroup( parent.getNavigablePath() ); - final String column = fetchable.getSelectionExpression(); final String table = fetchable.getContainingTableExpression(); + final String column; + + // In case of a formula we look for a result set position with the fetchable name + if ( fetchable.isFormula() ) { + column = fetchable.getFetchableName(); + } + else { + column = fetchable.getSelectionExpression(); + } final Expression expression = creationStateImpl.resolveSqlExpression( - createColumnReferenceKey( parentTableGroup.getTableReference( fetchPath, table ), column ), + createColumnReferenceKey( + parentTableGroup.getTableReference( fetchPath, table ), + fetchable.getSelectionExpression() + ), processingState -> { final int jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( column ); final int valuesArrayPosition = jdbcPositionToValuesArrayPosition( jdbcPosition ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEntity.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEntity.java index 47ef067465..e00a9c4609 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEntity.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEntity.java @@ -61,8 +61,7 @@ public class ImplicitModelPartResultBuilderEntity true, navigablePath, null, - () -> predicate -> { - }, + null, creationStateImpl, creationStateImpl.getCreationContext() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java index 8ac128d27a..6581529dbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/ResultSetMappingProcessor.java @@ -238,7 +238,7 @@ public class ResultSetMappingProcessor implements SQLQueryParser.ParserContext { final List columnNames; final String[] columnAliases = loadable.getSubclassPropertyColumnAliases( fetchBuilder.getFetchableName(), - suffix + alias2Suffix.get( fetchBuilder.getOwnerAlias() ) ); if ( columnAliases.length == 0 ) { final CollectionPersister collectionPersister = alias2CollectionPersister.get( fetchBuilder.getTableAlias() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java index 1491282efd..d403e31a05 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/MultiTableSqmMutationConverter.java @@ -106,8 +106,9 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter predicate -> { - }, + // We don't care about the discriminator predicate, + // but we pass non-null to ensure table reference join predicates are generated + () -> predicate -> {}, this, creationContext.getSessionFactory() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java index 8576b98794..ade06d229e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/mutation/internal/idtable/RestrictedDeleteExecutionDelegate.java @@ -331,7 +331,7 @@ public class RestrictedDeleteExecutionDelegate implements TableBasedDeleteHandle final Expression expression = sqlExpressionResolver.resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( targetTableReference, selection.getSelectionExpression() ), sqlAstProcessingState -> new ColumnReference( - rootTableGroup.getPrimaryTableReference(), + targetTableReference, selection, sessionFactory ) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java index 998130d6c5..35ba775955 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/RootTableGroupProducer.java @@ -12,6 +12,7 @@ import java.util.function.Supplier; import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.spi.SqlAliasBase; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.tree.predicate.Predicate; @@ -32,4 +33,12 @@ public interface RootTableGroupProducer extends TableGroupProducer, ModelPartCon String explicitSourceAlias, Supplier> additionalPredicateCollectorAccess, SqlAstCreationState creationState, SqlAstCreationContext creationContext); + + TableGroup createRootTableGroup( + boolean canUseInnerJoins, + NavigablePath navigablePath, + String explicitSourceAlias, + Supplier> additionalPredicateCollectorAccess, + SqlAliasBase sqlAliasBase, + SqlAstCreationState creationState, SqlAstCreationContext creationContext); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractResultSetAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractResultSetAccess.java index 68a6c84469..67033a34e7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractResultSetAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/AbstractResultSetAccess.java @@ -67,7 +67,7 @@ public abstract class AbstractResultSetAccess implements ResultSetAccess { catch (SQLException e) { throw getFactory().getJdbcServices().getJdbcEnvironment().getSqlExceptionHelper().convert( e, - "Unable to find column position by name" + "Unable to find column position by name: " + columnName ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaNativeQueryTest.java index 25889e9207..a3e3fff3ac 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaNativeQueryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/formula/FormulaNativeQueryTest.java @@ -6,6 +6,7 @@ */ package org.hibernate.orm.test.mapping.formula; +import java.util.Collections; import java.util.List; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -13,6 +14,7 @@ import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.annotations.Formula; +import org.hibernate.query.NativeQuery; import org.hibernate.query.Query; import org.hibernate.testing.TestForIssue; @@ -20,11 +22,13 @@ 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.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; /** * @author Алексей Макаров @@ -52,20 +56,75 @@ public class FormulaNativeQueryTest { @Test void testNativeQuery(SessionFactoryScope scope) { - scope.inTransaction( session -> { - final Query query = session.createNativeQuery( "SELECT ft.* FROM foo_table ft", Foo.class ); - final List list = query.getResultList(); - assertThat( list, hasSize( 3 ) ); - } ); + scope.inTransaction( + session -> { + final Query query = session.createNativeQuery( "SELECT ft.* FROM foo_table ft", Foo.class ); + try { + query.getResultList(); + Assertions.fail( + "the native query result mapping should fail if the formula field is not returned from the query" ); + } + catch (Exception ex) { + Assertions.assertTrue( ex.getMessage().contains( "distance" ) ); + } + } + ); + } + + @Test + public void testNativeQueryWithAllFields(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + Query query = session.createNativeQuery( + "SELECT ft.*, abs(locationEnd - locationStart) as distance FROM foo_table ft", + Foo.class + ); + List list = query.getResultList(); + assertThat( list, hasSize( 3 ) ); + } + ); + } + + @Test + public void testNativeQueryWithAliasProperties(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + NativeQuery query = session.createNativeQuery( + "SELECT ft.*, abs(ft.locationEnd - locationStart) as d FROM foo_table ft" ); + query.addRoot( "ft", Foo.class ) + .addProperty( "id", "id" ) + .addProperty( "locationStart", "locationStart" ) + .addProperty( "locationEnd", "locationEnd" ) + .addProperty( "distance", "d" ); + List list = query.getResultList(); + assertThat( list, hasSize( 3 ) ); + } + ); + } + + @Test + public void testNativeQueryWithAliasSyntax(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + NativeQuery query = session.createNativeQuery( + "SELECT ft.id as {ft.id}, ft.locationStart as {ft.locationStart}, ft.locationEnd as {ft.locationEnd}, abs(ft.locationEnd - locationStart) as {ft.distance} FROM foo_table ft" ) + .addEntity( "ft", Foo.class ); + query.setProperties( Collections.singletonMap( "distance", "distance" ) ); + List list = query.getResultList(); + assertThat( list, hasSize( 3 ) ); + } + ); } @Test void testHql(SessionFactoryScope scope) { - scope.inTransaction( session -> { - final Query query = session.createQuery( "SELECT ft FROM Foo ft", Foo.class ); - final List list = query.getResultList(); - assertThat( list, hasSize( 3 ) ); - } ); + scope.inTransaction( + session -> { + final Query query = session.createQuery( "SELECT ft FROM Foo ft", Foo.class ); + final List list = query.getResultList(); + assertThat( list, hasSize( 3 ) ); + } + ); } @Entity(name = "Foo") diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/autodiscovery/AutoDiscoveryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/autodiscovery/AutoDiscoveryTest.java index 07829e18b7..a5cf7cc021 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/autodiscovery/AutoDiscoveryTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/autodiscovery/AutoDiscoveryTest.java @@ -6,7 +6,6 @@ */ package org.hibernate.orm.test.sql.autodiscovery; -import javax.persistence.PersistenceException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -19,7 +18,6 @@ import org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl; import org.hibernate.cfg.Configuration; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; -import org.hibernate.loader.NonUniqueDiscoveredSqlAliasException; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Assert; @@ -59,31 +57,25 @@ public class AutoDiscoveryTest extends BaseCoreFunctionalTestCase { session.close(); session = openSession(); - try { - session.beginTransaction(); - List results = session.createNativeQuery( - "select u.name, u2.name from t_user u, t_user u2 where u.name='steve'" ).list(); - // this should result in a result set like: - // [0] steve, steve - // [1] steve, stliu - // although the rows could be reversed - assertEquals( 2, results.size() ); - final Object[] row1 = (Object[]) results.get( 0 ); - final Object[] row2 = (Object[]) results.get( 1 ); - assertEquals( "steve", row1[0] ); - assertEquals( "steve", row2[0] ); - if ( "steve".equals( row1[1] ) ) { - assertEquals( "stliu", row2[1] ); - } - else { - assertEquals( "stliu", row1[1] ); - } - session.getTransaction().commit(); + session.beginTransaction(); + List results = session.createNativeQuery( + "select u.name, u2.name from t_user u, t_user u2 where u.name='steve'" ).list(); + // this should result in a result set like: + // [0] steve, steve + // [1] steve, stliu + // although the rows could be reversed + assertEquals( 2, results.size() ); + final Object[] row1 = (Object[]) results.get( 0 ); + final Object[] row2 = (Object[]) results.get( 1 ); + assertEquals( "steve", row1[0] ); + assertEquals( "steve", row2[0] ); + if ( "steve".equals( row1[1] ) ) { + assertEquals( "stliu", row2[1] ); } - catch (PersistenceException e) { - //expected - assertTyping( NonUniqueDiscoveredSqlAliasException.class, e.getCause() ); + else { + assertEquals( "stliu", row1[1] ); } + session.getTransaction().commit(); session.close(); session = openSession();