re-enable tests

re-organize some tests
case expression and nested expression type inference
preliminary support for "type precedence"
This commit is contained in:
Steve Ebersole 2021-04-15 08:23:37 -05:00
parent 3958ee2360
commit c2f59beb64
6 changed files with 225 additions and 113 deletions

View File

@ -43,6 +43,7 @@ public interface MappingMetamodel {
* todo (6.0) : POC!!! Intended for use in SQM -> SQL translation * todo (6.0) : POC!!! Intended for use in SQM -> SQL translation
*/ */
MappingModelExpressable resolveMappingExpressable(SqmExpressable<?> sqmExpressable, Function<NavigablePath, TableGroup> tableGroupLocator); MappingModelExpressable resolveMappingExpressable(SqmExpressable<?> sqmExpressable, Function<NavigablePath, TableGroup> tableGroupLocator);
MappingModelExpressable lenientlyResolveMappingExpressable(SqmExpressable<?> sqmExpressable, Function<NavigablePath, TableGroup> tableGroupLocator);
/** /**
* Given a Java type, determine the corresponding AllowableParameterType to * Given a Java type, determine the corresponding AllowableParameterType to

View File

@ -708,6 +708,17 @@ public class MappingMetamodelImpl implements MappingMetamodel, MetamodelImplemen
return results.toArray( new String[results.size()] ); return results.toArray( new String[results.size()] );
} }
@Override
public MappingModelExpressable lenientlyResolveMappingExpressable(SqmExpressable<?> sqmExpressable, Function<NavigablePath, TableGroup> tableGroupLocator) {
try {
return resolveMappingExpressable( sqmExpressable, tableGroupLocator );
}
catch (UnsupportedOperationException e) {
return null;
}
}
@Override @Override
public MappingModelExpressable resolveMappingExpressable(SqmExpressable<?> sqmExpressable, Function<NavigablePath, TableGroup> tableGroupLocator) { public MappingModelExpressable resolveMappingExpressable(SqmExpressable<?> sqmExpressable, Function<NavigablePath, TableGroup> tableGroupLocator) {
if ( sqmExpressable instanceof SqmPath ) { if ( sqmExpressable instanceof SqmPath ) {

View File

@ -103,6 +103,7 @@ import org.hibernate.query.sqm.sql.internal.SqlAstQueryPartProcessingStateImpl;
import org.hibernate.query.sqm.sql.internal.SqmMapEntryResult; import org.hibernate.query.sqm.sql.internal.SqmMapEntryResult;
import org.hibernate.query.sqm.sql.internal.SqmParameterInterpretation; import org.hibernate.query.sqm.sql.internal.SqmParameterInterpretation;
import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation; import org.hibernate.query.sqm.sql.internal.SqmPathInterpretation;
import org.hibernate.query.sqm.sql.internal.TypeHelper;
import org.hibernate.query.sqm.tree.SqmStatement; import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.cte.SqmCteContainer; import org.hibernate.query.sqm.tree.cte.SqmCteContainer;
import org.hibernate.query.sqm.tree.cte.SqmCteStatement; import org.hibernate.query.sqm.tree.cte.SqmCteStatement;
@ -2432,6 +2433,16 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
); );
} }
protected MappingModelExpressable<?> lenientlyResolveMappingExpressable(SqmExpressable<?> nodeType) {
try {
return resolveMappingExpressable( nodeType );
}
catch (UnsupportedOperationException e) {
// todo (6.0) : log?
return null;
}
}
protected MappingModelExpressable<?> resolveMappingExpressable(SqmExpressable<?> nodeType) { protected MappingModelExpressable<?> resolveMappingExpressable(SqmExpressable<?> nodeType) {
final MappingModelExpressable valueMapping = getCreationContext().getDomainModel().resolveMappingExpressable( final MappingModelExpressable valueMapping = getCreationContext().getDomainModel().resolveMappingExpressable(
nodeType, nodeType,
@ -3547,60 +3558,121 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override @Override
public CaseSimpleExpression visitSimpleCaseExpression(SqmCaseSimple<?, ?> expression) { public CaseSimpleExpression visitSimpleCaseExpression(SqmCaseSimple<?, ?> expression) {
List<CaseSimpleExpression.WhenFragment> whenFragments = new ArrayList<>( expression.getWhenFragments().size() ); List<CaseSimpleExpression.WhenFragment> whenFragments = new ArrayList<>( expression.getWhenFragments().size() );
final MappingModelExpressable<?> alreadyKnown = creationContext
.getDomainModel()
.lenientlyResolveMappingExpressable( expression.getNodeType(), getFromClauseIndex()::findTableGroup );
MappingModelExpressable<?> resolved = alreadyKnown;
inferrableTypeAccessStack.push( () -> alreadyKnown );
Expression otherwise = null;
try {
for ( SqmCaseSimple.WhenFragment<?, ?> whenFragment : expression.getWhenFragments() ) { for ( SqmCaseSimple.WhenFragment<?, ?> whenFragment : expression.getWhenFragments() ) {
final Expression resultExpression = (Expression) whenFragment.getResult().accept( this );
resolved = TypeHelper.highestPrecedence( resolved, resultExpression.getExpressionType() );
whenFragments.add( whenFragments.add(
new CaseSimpleExpression.WhenFragment( new CaseSimpleExpression.WhenFragment(
(Expression) whenFragment.getCheckValue().accept( this ), (Expression) whenFragment.getCheckValue().accept( this ),
visitWithInferredType( whenFragment.getResult(), expression ) resultExpression
) )
); );
} }
Expression otherwise = null;
if ( expression.getOtherwise() != null ) { if ( expression.getOtherwise() != null ) {
otherwise = visitWithInferredType( expression.getOtherwise(), expression ); otherwise = (Expression) expression.getOtherwise().accept( this );
resolved = TypeHelper.highestPrecedence( resolved, otherwise.getExpressionType() );
}
}
finally {
inferrableTypeAccessStack.pop();
} }
final CaseSimpleExpression result = new CaseSimpleExpression( return new CaseSimpleExpression(
resolveMappingExpressable( expression.getNodeType() ), resolved,
(Expression) expression.getFixture().accept( this ), (Expression) expression.getFixture().accept( this ),
whenFragments, whenFragments,
otherwise otherwise
); );
return result;
} }
@Override @Override
public CaseSearchedExpression visitSearchedCaseExpression(SqmCaseSearched<?> expression) { public CaseSearchedExpression visitSearchedCaseExpression(SqmCaseSearched<?> expression) {
List<CaseSearchedExpression.WhenFragment> whenFragments = new ArrayList<>( expression.getWhenFragments().size() ); final List<CaseSearchedExpression.WhenFragment> whenFragments = new ArrayList<>( expression.getWhenFragments().size() );
for ( SqmCaseSearched.WhenFragment<?> whenFragment : expression.getWhenFragments() ) {
whenFragments.add( final MappingModelExpressable<?> alreadyKnown = creationContext
new CaseSearchedExpression.WhenFragment( .getDomainModel()
(Predicate) whenFragment.getPredicate().accept( this ), .lenientlyResolveMappingExpressable( expression.getNodeType(), getFromClauseIndex()::findTableGroup );
visitWithInferredType( whenFragment.getResult(), expression ) MappingModelExpressable<?> resolved = alreadyKnown;
)
); inferrableTypeAccessStack.push( () -> alreadyKnown );
}
Expression otherwise = null; Expression otherwise = null;
try {
for ( SqmCaseSearched.WhenFragment<?> whenFragment : expression.getWhenFragments() ) {
final Predicate whenPredicate = (Predicate) whenFragment.getPredicate().accept( this );
final Expression resultExpression = (Expression) whenFragment.getResult().accept( this );
resolved = TypeHelper.highestPrecedence( resolved, resultExpression.getExpressionType() );
whenFragments.add( new CaseSearchedExpression.WhenFragment( whenPredicate, resultExpression ) );
}
if ( expression.getOtherwise() != null ) { if ( expression.getOtherwise() != null ) {
otherwise = visitWithInferredType( expression.getOtherwise(), expression ); otherwise = (Expression) expression.getOtherwise().accept( this );
resolved = TypeHelper.highestPrecedence( resolved, otherwise.getExpressionType() );
}
}
finally {
inferrableTypeAccessStack.pop();
} }
final CaseSearchedExpression result = new CaseSearchedExpression( return new CaseSearchedExpression( resolved, whenFragments, otherwise );
resolveMappingExpressable( expression.getNodeType() ),
whenFragments,
otherwise
);
return result;
} }
private <T> T visitWithInferredType(SqmExpression<?> expression, SqmExpression<?> inferred) { private <X> X visitWithInferredType(SqmExpression<?> expression, SqmExpression<?> inferred) {
inferrableTypeAccessStack.push( () -> determineValueMapping( inferred ) ); inferrableTypeAccessStack.push( () -> determineValueMapping( inferred ) );
try { try {
return (T) expression.accept( this ); return (X) expression.accept( this );
}
finally {
inferrableTypeAccessStack.pop();
}
}
private <X> X visitWithLenientInferredType(SqmExpression<?> expression, SqmExpression<?> inferred) {
inferrableTypeAccessStack.push(
() -> {
try {
final MappingModelExpressable<?> definedType = creationContext
.getDomainModel()
.resolveMappingExpressable( expression.getNodeType(), getFromClauseIndex()::findTableGroup );
if ( definedType != null ) {
return definedType;
}
}
catch (UnsupportedOperationException ignore) {
// todo (6.0) : log?
}
try {
final MappingModelExpressable<?> definedType = creationContext
.getDomainModel()
.lenientlyResolveMappingExpressable( inferred.getNodeType(), getFromClauseIndex()::findTableGroup );
if ( definedType != null ) {
return definedType;
}
}
catch (UnsupportedOperationException ignore) {
// todo (6.0) : log?
}
return null;
}
);
try {
return (X) expression.accept( this );
} }
finally { finally {
inferrableTypeAccessStack.pop(); inferrableTypeAccessStack.pop();

View File

@ -0,0 +1,41 @@
/*
* 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.query.sqm.sql.internal;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.sqm.SqmExpressable;
import org.hibernate.query.sqm.tree.domain.SqmPath;
/**
* @author Steve Ebersole
*/
public class TypeHelper {
public static MappingModelExpressable<?> highestPrecedence(MappingModelExpressable<?> type1, MappingModelExpressable<?> type2) {
if ( type1 == null ) {
return type2;
}
if ( type2 == null ) {
return type1;
}
if ( type1 instanceof ModelPart ) {
return type1;
}
if ( type2 instanceof ModelPart ) {
return type2;
}
// todo (6.0) : we probably want a precedence based on generic resolutions such as those based on Serializable
// todo (6.0) : anything else to consider?
return type1;
}
}

View File

@ -10,6 +10,8 @@ package org.hibernate.sql.ast.tree.expression;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.MappingModelExpressable; import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer; import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.SqlAstWalker;
@ -24,17 +26,17 @@ import org.hibernate.type.BasicType;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class CaseSearchedExpression implements Expression, DomainResultProducer { public class CaseSearchedExpression implements Expression, DomainResultProducer {
private final BasicType type; private final BasicValuedMapping type;
private List<WhenFragment> whenFragments = new ArrayList<>(); private List<WhenFragment> whenFragments = new ArrayList<>();
private Expression otherwise; private Expression otherwise;
public CaseSearchedExpression(MappingModelExpressable type) { public CaseSearchedExpression(MappingModelExpressable type) {
this.type = (BasicType) type; this.type = (BasicValuedMapping) type;
} }
public CaseSearchedExpression(MappingModelExpressable type, List<WhenFragment> whenFragments, Expression otherwise) { public CaseSearchedExpression(MappingModelExpressable type, List<WhenFragment> whenFragments, Expression otherwise) {
this.type = (BasicType) type; this.type = (BasicValuedMapping) type;
this.whenFragments = whenFragments; this.whenFragments = whenFragments;
this.otherwise = otherwise; this.otherwise = otherwise;
} }

View File

@ -17,12 +17,16 @@ import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction; import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
/** /**
@ -35,6 +39,14 @@ public class CaseStatementTest extends BaseCoreFunctionalTestCase {
@Id @Id
private Integer id; private Integer id;
private String name; private String name;
private Person() {
}
public Person(Integer id, String name) {
this.id = id;
this.name = name;
}
} }
@Override @Override
@ -69,56 +81,26 @@ public class CaseStatementTest extends BaseCoreFunctionalTestCase {
@Test @Test
public void testSimpleCaseStatementWithParamAllResults() { public void testSimpleCaseStatementWithParamAllResults() {
try ( final SessionImplementor s = (SessionImplementor) openSession() ) {
inTransaction(
s,
session-> {
try {
s.createQuery( "select case p.name when 'Steve' then :opt1 else :opt2 end from Person p" )
.setParameter( "opt1", "x" )
.setParameter( "opt2", "y" )
.list();
fail( "was expecting an exception" );
}
catch (IllegalArgumentException e) {
assertTyping( QueryException.class, e.getCause() );
}
catch (QueryException expected) {
// expected
}
}
);
inTransaction( inTransaction(
s, (session) -> {
session-> { session.createQuery( "select case p.name when 'Steve' then :opt1 else :opt2 end from Person p" )
s.createQuery( "select case p.name when 'Steve' then cast( :opt1 as string ) else cast( :opt2 as string) end from Person p" )
.setParameter( "opt1", "x" ) .setParameter( "opt1", "x" )
.setParameter( "opt2", "y" ) .setParameter( "opt2", "y" )
.list(); .list();
}
);
inTransaction( session.createQuery( "select case p.name when 'Steve' then cast( :opt1 as string ) else cast( :opt2 as string) end from Person p" )
s, .setParameter( "opt1", "x" )
session -> { .setParameter( "opt2", "y" )
try { .list();
s.createQuery( "select case p.name when 'Steve' then :opt1 else :opt2 end from Person p" )
session.createQuery( "select case p.name when 'Steve' then :opt1 else :opt2 end from Person p" )
.setParameter( "opt1", "x" ) .setParameter( "opt1", "x" )
.setParameter( "opt2", "y" ) .setParameter( "opt2", "y" )
.list(); .list();
fail( "was expecting an exception" );
}
catch (IllegalArgumentException e) {
assertTyping( QueryException.class, e.getCause() );
}
catch (QueryException expected) {
// expected
}
} }
); );
} }
}
@Test @Test
public void testSearchedCaseStatementFixture() { public void testSearchedCaseStatementFixture() {
@ -170,36 +152,39 @@ public class CaseStatementTest extends BaseCoreFunctionalTestCase {
@Test @Test
public void testSearchedCaseStatementWithAllParamResults() { public void testSearchedCaseStatementWithAllParamResults() {
try ( final SessionImplementor s = (SessionImplementor) openSession() ) {
inTransaction( inTransaction(
s, (session) -> {
session-> { session.persist( new Person( 1, "Steve" ) );
try {
s.createQuery( "select case when p.name = 'Steve' then :opt1 else :opt2 end from Person p" )
.setParameter( "opt1", "x" )
.setParameter( "opt2", "y" )
.list();
fail( "was expecting an exception" );
}
catch (IllegalArgumentException e) {
assertTyping( QueryException.class, e.getCause() );
}
catch (QueryException expected) {
// expected
}
} }
); );
inTransaction( inTransaction(
s, (session) -> {
session-> { final List list = session.createQuery( "select case when p.name = 'Steve' then :opt1 else :opt2 end from Person p" )
s.createQuery( "select case when p.name = 'Steve' then cast( :opt1 as string) else :opt2 end from Person p" )
.setParameter( "opt1", "x" ) .setParameter( "opt1", "x" )
.setParameter( "opt2", "y" ) .setParameter( "opt2", "y" )
.list(); .list();
assertThat( list.size(), is( 1 ) );
assertThat( list.get( 0 ), is( "x" ) );
}
);
inTransaction(
(session) -> {
final List list = session.createQuery( "select case when p.name = 'Steve' then cast( :opt1 as string) else :opt2 end from Person p" )
.setParameter( "opt1", "x" )
.setParameter( "opt2", "y" )
.list();
assertThat( list.size(), is( 1 ) );
assertThat( list.get( 0 ), is( "x" ) );
} }
); );
} }
@After
public void dropTestData() {
inTransaction(
(session) -> session.createQuery( "delete Person" ).executeUpdate()
);
} }
} }