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
*/
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

View File

@ -708,6 +708,17 @@ public class MappingMetamodelImpl implements MappingMetamodel, MetamodelImplemen
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
public MappingModelExpressable resolveMappingExpressable(SqmExpressable<?> sqmExpressable, Function<NavigablePath, TableGroup> tableGroupLocator) {
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.SqmParameterInterpretation;
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.cte.SqmCteContainer;
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) {
final MappingModelExpressable valueMapping = getCreationContext().getDomainModel().resolveMappingExpressable(
nodeType,
@ -3547,60 +3558,121 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override
public CaseSimpleExpression visitSimpleCaseExpression(SqmCaseSimple<?, ?> expression) {
List<CaseSimpleExpression.WhenFragment> whenFragments = new ArrayList<>( expression.getWhenFragments().size() );
for ( SqmCaseSimple.WhenFragment<?, ?> whenFragment : expression.getWhenFragments() ) {
whenFragments.add(
new CaseSimpleExpression.WhenFragment(
(Expression) whenFragment.getCheckValue().accept( this ),
visitWithInferredType( whenFragment.getResult(), expression )
)
);
}
final MappingModelExpressable<?> alreadyKnown = creationContext
.getDomainModel()
.lenientlyResolveMappingExpressable( expression.getNodeType(), getFromClauseIndex()::findTableGroup );
MappingModelExpressable<?> resolved = alreadyKnown;
inferrableTypeAccessStack.push( () -> alreadyKnown );
Expression otherwise = null;
if ( expression.getOtherwise() != null ) {
otherwise = visitWithInferredType( expression.getOtherwise(), expression );
try {
for ( SqmCaseSimple.WhenFragment<?, ?> whenFragment : expression.getWhenFragments() ) {
final Expression resultExpression = (Expression) whenFragment.getResult().accept( this );
resolved = TypeHelper.highestPrecedence( resolved, resultExpression.getExpressionType() );
whenFragments.add(
new CaseSimpleExpression.WhenFragment(
(Expression) whenFragment.getCheckValue().accept( this ),
resultExpression
)
);
}
if ( expression.getOtherwise() != null ) {
otherwise = (Expression) expression.getOtherwise().accept( this );
resolved = TypeHelper.highestPrecedence( resolved, otherwise.getExpressionType() );
}
}
finally {
inferrableTypeAccessStack.pop();
}
final CaseSimpleExpression result = new CaseSimpleExpression(
resolveMappingExpressable( expression.getNodeType() ),
return new CaseSimpleExpression(
resolved,
(Expression) expression.getFixture().accept( this ),
whenFragments,
otherwise
);
return result;
}
@Override
public CaseSearchedExpression visitSearchedCaseExpression(SqmCaseSearched<?> expression) {
List<CaseSearchedExpression.WhenFragment> whenFragments = new ArrayList<>( expression.getWhenFragments().size() );
for ( SqmCaseSearched.WhenFragment<?> whenFragment : expression.getWhenFragments() ) {
whenFragments.add(
new CaseSearchedExpression.WhenFragment(
(Predicate) whenFragment.getPredicate().accept( this ),
visitWithInferredType( whenFragment.getResult(), expression )
)
);
}
final List<CaseSearchedExpression.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;
if ( expression.getOtherwise() != null ) {
otherwise = visitWithInferredType( expression.getOtherwise(), expression );
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 ) {
otherwise = (Expression) expression.getOtherwise().accept( this );
resolved = TypeHelper.highestPrecedence( resolved, otherwise.getExpressionType() );
}
}
finally {
inferrableTypeAccessStack.pop();
}
final CaseSearchedExpression result = new CaseSearchedExpression(
resolveMappingExpressable( expression.getNodeType() ),
whenFragments,
otherwise
);
return result;
return new CaseSearchedExpression( resolved, whenFragments, otherwise );
}
private <T> T visitWithInferredType(SqmExpression<?> expression, SqmExpression<?> inferred) {
private <X> X visitWithInferredType(SqmExpression<?> expression, SqmExpression<?> inferred) {
inferrableTypeAccessStack.push( () -> determineValueMapping( inferred ) );
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 {
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.List;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.SqlAstWalker;
@ -24,17 +26,17 @@ import org.hibernate.type.BasicType;
* @author Steve Ebersole
*/
public class CaseSearchedExpression implements Expression, DomainResultProducer {
private final BasicType type;
private final BasicValuedMapping type;
private List<WhenFragment> whenFragments = new ArrayList<>();
private Expression otherwise;
public CaseSearchedExpression(MappingModelExpressable type) {
this.type = (BasicType) type;
this.type = (BasicValuedMapping) type;
}
public CaseSearchedExpression(MappingModelExpressable type, List<WhenFragment> whenFragments, Expression otherwise) {
this.type = (BasicType) type;
this.type = (BasicValuedMapping) type;
this.whenFragments = whenFragments;
this.otherwise = otherwise;
}

View File

@ -17,12 +17,16 @@ import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
@ -35,6 +39,14 @@ public class CaseStatementTest extends BaseCoreFunctionalTestCase {
@Id
private Integer id;
private String name;
private Person() {
}
public Person(Integer id, String name) {
this.id = id;
this.name = name;
}
}
@Override
@ -69,55 +81,25 @@ public class CaseStatementTest extends BaseCoreFunctionalTestCase {
@Test
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(
s,
session-> {
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( "opt2", "y" )
.list();
}
);
inTransaction(
(session) -> {
session.createQuery( "select case p.name when 'Steve' then :opt1 else :opt2 end from Person p" )
.setParameter( "opt1", "x" )
.setParameter( "opt2", "y" )
.list();
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
}
}
);
}
session.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( "opt2", "y" )
.list();
session.createQuery( "select case p.name when 'Steve' then :opt1 else :opt2 end from Person p" )
.setParameter( "opt1", "x" )
.setParameter( "opt2", "y" )
.list();
}
);
}
@Test
@ -170,36 +152,39 @@ public class CaseStatementTest extends BaseCoreFunctionalTestCase {
@Test
public void testSearchedCaseStatementWithAllParamResults() {
try ( final SessionImplementor s = (SessionImplementor) openSession() ) {
inTransaction(
s,
session-> {
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(
(session) -> {
session.persist( new Person( 1, "Steve" ) );
}
);
inTransaction(
s,
session-> {
s.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();
inTransaction(
(session) -> {
final List list = session.createQuery( "select case when p.name = 'Steve' then :opt1 else :opt2 end from Person p" )
.setParameter( "opt1", "x" )
.setParameter( "opt2", "y" )
.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()
);
}
}