HHH-16180 - Add test and fix (AssertionError when using using native query on table with InheritanceStrategy.JOINED)

Signed-off-by: Jan Schatteman <jschatte@redhat.com>
This commit is contained in:
Jan Schatteman 2023-02-17 22:27:43 +01:00 committed by Christian Beikov
parent c67dbc0013
commit 2b4201e413
3 changed files with 166 additions and 54 deletions

View File

@ -119,60 +119,7 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
} }
private Expression createCaseSearchedExpression(TableGroup entityTableGroup) { private Expression createCaseSearchedExpression(TableGroup entityTableGroup) {
return new SelfRenderingExpression() { return new CaseStatementDiscriminatorExpression( entityTableGroup );
CaseSearchedExpression caseSearchedExpression;
@Override
public void renderToSql(
SqlAppender sqlAppender,
SqlAstTranslator<?> walker,
SessionFactoryImplementor sessionFactory) {
if ( caseSearchedExpression == null ) {
// todo (6.0): possible optimization is to omit cases for table reference joins, that touch a super class, where a subclass is inner joined due to pruning
caseSearchedExpression = new CaseSearchedExpression( CaseStatementDiscriminatorMappingImpl.this );
tableDiscriminatorDetailsMap.forEach(
(tableName, tableDiscriminatorDetails) -> {
final TableReference tableReference = entityTableGroup.getTableReference(
entityTableGroup.getNavigablePath(),
tableName,
false,
false
);
if ( tableReference == null ) {
// assume this is because it is a table that is not part of the processing entity's sub-hierarchy
return;
}
final Predicate predicate = new NullnessPredicate(
new ColumnReference(
tableReference,
tableDiscriminatorDetails.getCheckColumnName(),
false,
null,
getJdbcMapping()
),
true
);
caseSearchedExpression.when(
predicate,
new QueryLiteral<>(
tableDiscriminatorDetails.getDiscriminatorValue(),
getUnderlyingJdbcMappingType()
)
);
}
);
}
caseSearchedExpression.accept( walker );
}
@Override
public JdbcMappingContainer getExpressionType() {
return CaseStatementDiscriminatorMappingImpl.this;
}
};
} }
@Override @Override
@ -281,4 +228,63 @@ public class CaseStatementDiscriminatorMappingImpl extends AbstractDiscriminator
} }
} }
public final class CaseStatementDiscriminatorExpression implements SelfRenderingExpression {
private final TableGroup entityTableGroup;
CaseSearchedExpression caseSearchedExpression;
public CaseStatementDiscriminatorExpression(TableGroup entityTableGroup) {
this.entityTableGroup = entityTableGroup;
}
@Override
public void renderToSql(
SqlAppender sqlAppender,
SqlAstTranslator<?> walker,
SessionFactoryImplementor sessionFactory) {
if ( caseSearchedExpression == null ) {
// todo (6.0): possible optimization is to omit cases for table reference joins, that touch a super class, where a subclass is inner joined due to pruning
caseSearchedExpression = new CaseSearchedExpression( CaseStatementDiscriminatorMappingImpl.this );
tableDiscriminatorDetailsMap.forEach(
(tableName, tableDiscriminatorDetails) -> {
final TableReference tableReference = entityTableGroup.getTableReference(
entityTableGroup.getNavigablePath(),
tableName,
false,
false
);
if ( tableReference == null ) {
// assume this is because it is a table that is not part of the processing entity's sub-hierarchy
return;
}
final Predicate predicate = new NullnessPredicate(
new ColumnReference(
tableReference,
tableDiscriminatorDetails.getCheckColumnName(),
false,
null,
getJdbcMapping()
),
true
);
caseSearchedExpression.when(
predicate,
new QueryLiteral<>(
tableDiscriminatorDetails.getDiscriminatorValue(),
getUnderlyingJdbcMappingType()
)
);
}
);
}
caseSearchedExpression.accept( walker );
}
@Override
public JdbcMappingContainer getExpressionType() {
return CaseStatementDiscriminatorMappingImpl.this;
}
}
} }

View File

@ -28,6 +28,7 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping;
import org.hibernate.metamodel.mapping.internal.BasicValuedCollectionPart; import org.hibernate.metamodel.mapping.internal.BasicValuedCollectionPart;
import org.hibernate.metamodel.mapping.internal.CaseStatementDiscriminatorMappingImpl;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.spi.EntityIdentifierNavigablePath; import org.hibernate.spi.EntityIdentifierNavigablePath;
@ -65,6 +66,7 @@ import static org.hibernate.query.results.ResultsHelper.attributeName;
public class DomainResultCreationStateImpl public class DomainResultCreationStateImpl
implements DomainResultCreationState, SqlAstCreationState, SqlAstProcessingState, SqlExpressionResolver { implements DomainResultCreationState, SqlAstCreationState, SqlAstProcessingState, SqlExpressionResolver {
private static final String DISCRIMINATOR_ALIAS = "clazz_";
private final String stateIdentifier; private final String stateIdentifier;
private final FromClauseAccessImpl fromClauseAccess; private final FromClauseAccessImpl fromClauseAccess;
@ -295,6 +297,26 @@ public class DomainResultCreationStateImpl
return sqlSelection; return sqlSelection;
} }
else if ( created instanceof CaseStatementDiscriminatorMappingImpl.CaseStatementDiscriminatorExpression ) {
final int valuesArrayPosition;
if ( nestingFetchParent != null ) {
valuesArrayPosition = nestingFetchParent.getReferencedMappingType().getSelectableIndex( DISCRIMINATOR_ALIAS );
}
else {
final int jdbcPosition = jdbcResultsMetadata.resolveColumnPosition( DISCRIMINATOR_ALIAS );
valuesArrayPosition = ResultsHelper.jdbcPositionToValuesArrayPosition( jdbcPosition );
}
final ResultSetMappingSqlSelection sqlSelection = new ResultSetMappingSqlSelection(
valuesArrayPosition,
created.getExpressionType().getSingleJdbcMapping()
);
sqlSelectionMap.put( key, sqlSelection );
sqlSelectionConsumer.accept( sqlSelection );
return sqlSelection;
}
return created; return created;
} }

View File

@ -0,0 +1,84 @@
/*
* 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.hql.joinedSubclass;
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.Assertions;
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.Inheritance;
import jakarta.persistence.InheritanceType;
/**
* @author Jan Schatteman
*/
@DomainModel(
annotatedClasses = {
JoinedSubclassNativeQueryTest.Person.class
}
)
@SessionFactory
public class JoinedSubclassNativeQueryTest {
@BeforeAll
public void setup(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Person p = new Person();
p.setFirstName( "Jan" );
session.persist( p );
}
);
}
@AfterAll
public void tearDown(SessionFactoryScope scope) {
scope.inTransaction(
session -> session.createMutationQuery( "delete from Person" ).executeUpdate()
);
}
@Test
@TestForIssue( jiraKey = "HHH-16180")
public void testJoinedInheritanceNativeQuery(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Person p = session.createNativeQuery( "select p.*, 0 as clazz_ from Person p", Person.class ).getSingleResult();
Assertions.assertNotNull( p );
Assertions.assertEquals( p.getFirstName(), "Jan" );
}
);
}
@Entity(name = "Person")
@Inheritance(strategy = InheritanceType.JOINED)
public static class Person {
@Id
@GeneratedValue
private Long id;
@Basic(optional = false)
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
}