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 3c753c1f28ea..a0ba547c2c55 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 @@ -1876,62 +1876,62 @@ public String selectFragment(String alias, String suffix) { // Wrap expressions with aliases final SelectClause selectClause = rootQuerySpec.getSelectClause(); final List sqlSelections = selectClause.getSqlSelections(); + final Set processedExpressions = new HashSet<>( sqlSelections.size() ); int i = 0; - int columnIndex = 0; - final String[] columnAliases = getSubclassColumnAliasClosure(); - final int columnAliasesSize = columnAliases.length; - for ( String identifierAlias : identifierAliases ) { - sqlSelections.set( - i, - new SqlSelectionImpl( - i, - new AliasedExpression( sqlSelections.get( i ).getExpression(), identifierAlias + suffix ) - ) - ); - if ( i < columnAliasesSize && columnAliases[i].equals( identifierAlias ) ) { - columnIndex++; + final int identifierSelectionSize = identifierMapping.getJdbcTypeCount(); + for ( int j = 0; j < identifierSelectionSize; j++ ) { + final SelectableMapping selectableMapping = identifierMapping.getSelectable( j ); + if ( processedExpressions.add( selectableMapping.getSelectionExpression() ) ) { + aliasSelection( sqlSelections, i, identifierAliases[j] + suffix ); + i++; } - i++; } - if ( entityMetamodel.hasSubclasses() ) { - sqlSelections.set( - i, - new SqlSelectionImpl( - i, - new AliasedExpression( sqlSelections.get( i ).getExpression(), getDiscriminatorAlias() + suffix ) - ) - ); - i++; + if ( hasSubclasses() ) { + assert discriminatorMapping.getJdbcTypeCount() == 1; + final SelectableMapping selectableMapping = discriminatorMapping.getSelectable( 0 ); + if ( processedExpressions.add( selectableMapping.getSelectionExpression() ) ) { + aliasSelection( sqlSelections, i, getDiscriminatorAlias() + suffix ); + i++; + } } if ( hasRowId() ) { - sqlSelections.set( - i, - new SqlSelectionImpl( - i, - new AliasedExpression( sqlSelections.get( i ).getExpression(), ROWID_ALIAS + suffix ) - ) - ); - i++; + final SelectableMapping selectableMapping = rowIdMapping; + if ( processedExpressions.add( selectableMapping.getSelectionExpression() ) ) { + aliasSelection( sqlSelections, i, ROWID_ALIAS + suffix ); + i++; + } } + final String[] columnAliases = getSubclassColumnAliasClosure(); final String[] formulaAliases = getSubclassFormulaAliasClosure(); + int columnIndex = 0; int formulaIndex = 0; - for ( ; i < sqlSelections.size(); i++ ) { - final SqlSelection sqlSelection = sqlSelections.get( i ); - final ColumnReference columnReference = (ColumnReference) sqlSelection.getExpression(); - final String selectAlias = - columnReference.isColumnExpressionFormula() - ? formulaAliases[formulaIndex++] + suffix - : columnAliases[columnIndex++] + suffix; - sqlSelections.set( - i, - new SqlSelectionImpl( - sqlSelection.getValuesArrayPosition(), - new AliasedExpression( sqlSelection.getExpression(), selectAlias ) - ) - ); + final int size = getNumberOfFetchables(); + // getSubclassColumnAliasClosure contains the _identifierMapper columns when it has an id class, + // which need to be skipped + if ( identifierMapping instanceof NonAggregatedIdentifierMapping nonAggregatedIdentifierMapping + && nonAggregatedIdentifierMapping.getIdClassEmbeddable() != null ) { + columnIndex = identifierSelectionSize; + } + for ( int j = 0; j < size; j++ ) { + final AttributeMapping fetchable = getFetchable( j ); + if ( !(fetchable instanceof PluralAttributeMapping) + && !skipFetchable( fetchable, fetchable.getMappedFetchOptions().getTiming() ) + && fetchable.isSelectable() ) { + final int jdbcTypeCount = fetchable.getJdbcTypeCount(); + for ( int k = 0; k < jdbcTypeCount; k++ ) { + final SelectableMapping selectableMapping = fetchable.getSelectable( k ); + if ( processedExpressions.add( selectableMapping.getSelectionExpression() ) ) { + final String baseAlias = selectableMapping.isFormula() + ? formulaAliases[formulaIndex++] + : columnAliases[columnIndex++]; + aliasSelection( sqlSelections, i, baseAlias + suffix ); + i++; + } + } + } } final String sql = @@ -1945,6 +1945,17 @@ public String selectFragment(String alias, String suffix) { : sql.substring( "select ".length() ); } + private static void aliasSelection( + List sqlSelections, + int selectionIndex, + String alias) { + final Expression expression = sqlSelections.get( selectionIndex ).getExpression(); + sqlSelections.set( + selectionIndex, + new SqlSelectionImpl( selectionIndex, new AliasedExpression( expression, alias ) ) + ); + } + private ImmutableFetchList fetchProcessor(FetchParent fetchParent, LoaderSqlAstCreationState creationState) { final FetchableContainer fetchableContainer = fetchParent.getReferencedMappingContainer(); final int size = fetchableContainer.getNumberOfFetchables(); @@ -5902,7 +5913,7 @@ public Fetchable getKeyFetchable(int position) { } @Override - public Fetchable getFetchable(int position) { + public AttributeMapping getFetchable(int position) { return getStaticFetchableList().get( position ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderColumnDeduplicationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderColumnDeduplicationTest.java new file mode 100644 index 000000000000..0e327fbc9f22 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/sql/NativeQueryResultBuilderColumnDeduplicationTest.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.query.sql; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +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; + +@DomainModel(annotatedClasses = { + NativeQueryResultBuilderColumnDeduplicationTest.MyEntity.class +}) +@SessionFactory +@Jira("https://hibernate.atlassian.net/browse/HHH-19712") +public class NativeQueryResultBuilderColumnDeduplicationTest { + + @Test + public void test(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createNativeQuery( "select {t.*} from MyEntity t", Object.class ) + .addEntity( "t", MyEntity.class ) + .getResultList(); + } + ); + } + + @Entity(name = "MyEntity") + public static class MyEntity { + @EmbeddedId + private MyEntityPk id; + @Column(insertable = false, updatable = false) + private String name; + private String description; + } + + @Embeddable + public static class MyEntityPk { + private String id; + private String name; + } +}