HHH-15106 - fk() SQM function
This commit is contained in:
parent
6e6cc5f06e
commit
362b4c0ac7
|
@ -139,6 +139,7 @@ ID : [iI][dD];
|
|||
VERSION : [vV] [eE] [rR] [sS] [iI] [oO] [nN];
|
||||
VERSIONED : [vV] [eE] [rR] [sS] [iI] [oO] [nN] [eE] [dD];
|
||||
NATURALID : [nN] [aA] [tT] [uU] [rR] [aA] [lL] [iI] [dD];
|
||||
FK : [fF] [kK];
|
||||
|
||||
ALL : [aA] [lL] [lL];
|
||||
AND : [aA] [nN] [dD];
|
||||
|
|
|
@ -671,6 +671,7 @@ primaryExpression
|
|||
| entityIdReference # EntityIdExpression
|
||||
| entityVersionReference # EntityVersionExpression
|
||||
| entityNaturalIdReference # EntityNaturalIdExpression
|
||||
| toOneFkReference # ToOneFkExpression
|
||||
| syntacticDomainPath pathContinuation? # SyntacticPathExpression
|
||||
| function # FunctionExpression
|
||||
| generalPathFragment # GeneralPathExpression
|
||||
|
@ -737,6 +738,13 @@ entityNaturalIdReference
|
|||
: NATURALID LEFT_PAREN path RIGHT_PAREN pathContinuation?
|
||||
;
|
||||
|
||||
/**
|
||||
* The special function 'fk()'
|
||||
*/
|
||||
toOneFkReference
|
||||
: FK LEFT_PAREN path RIGHT_PAREN
|
||||
;
|
||||
|
||||
/**
|
||||
* A 'case' expression, which comes in two forms: "simple", and "searched"
|
||||
*/
|
||||
|
|
|
@ -51,6 +51,7 @@ import org.hibernate.metamodel.internal.JpaMetaModelPopulationSetting;
|
|||
import org.hibernate.metamodel.internal.JpaStaticMetaModelPopulationSetting;
|
||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess;
|
||||
import org.hibernate.metamodel.model.domain.BasicDomainType;
|
||||
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.metamodel.model.domain.ManagedDomainType;
|
||||
|
@ -774,6 +775,11 @@ public class MappingMetamodelImpl implements MappingMetamodelImplementor, Metamo
|
|||
return (BasicType<?>) sqmExpressible;
|
||||
}
|
||||
|
||||
if ( sqmExpressible instanceof BasicDomainType ) {
|
||||
final BasicDomainType<?> domainType = (BasicDomainType<?>) sqmExpressible;
|
||||
return getTypeConfiguration().getBasicTypeForJavaType( domainType.getExpressibleJavaType().getJavaTypeClass() );
|
||||
}
|
||||
|
||||
if ( sqmExpressible instanceof BasicSqmPathSource<?> ) {
|
||||
return getTypeConfiguration().getBasicTypeForJavaType(((BasicSqmPathSource<?>) sqmExpressible).getJavaType());
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ import org.hibernate.metamodel.model.domain.ManagedDomainType;
|
|||
import org.hibernate.metamodel.model.domain.PersistentAttribute;
|
||||
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
|
||||
import org.hibernate.metamodel.model.domain.SingularPersistentAttribute;
|
||||
import org.hibernate.metamodel.model.domain.internal.EntitySqmPathSource;
|
||||
import org.hibernate.query.PathException;
|
||||
import org.hibernate.query.ReturnableType;
|
||||
import org.hibernate.query.SemanticException;
|
||||
|
@ -102,6 +103,8 @@ import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
|
|||
import org.hibernate.query.sqm.tree.domain.AbstractSqmFrom;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmListJoin;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
|
||||
|
@ -191,6 +194,7 @@ import org.hibernate.type.descriptor.java.PrimitiveByteArrayJavaType;
|
|||
import org.jboss.logging.Logger;
|
||||
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.metamodel.Bindable;
|
||||
import jakarta.persistence.metamodel.SingularAttribute;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
@ -2275,6 +2279,34 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
|
|||
throw new SemanticException( "Path does not resolve to an entity type '" + sqmPath.getNavigablePath().getFullPath() + "'" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitToOneFkExpression(HqlParser.ToOneFkExpressionContext ctx) {
|
||||
return visitToOneFkReference( (HqlParser.ToOneFkReferenceContext) ctx.getChild( 0 ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmFkExpression<?> visitToOneFkReference(HqlParser.ToOneFkReferenceContext ctx) {
|
||||
final SqmPath<Object> sqmPath = consumeDomainPath( (HqlParser.PathContext) ctx.getChild( 2 ) );
|
||||
final SqmPathSource<?> toOneReference = sqmPath.getReferencedPathSource();
|
||||
|
||||
final boolean validToOneRef = toOneReference.getBindableType() == Bindable.BindableType.SINGULAR_ATTRIBUTE
|
||||
&& toOneReference instanceof EntitySqmPathSource;
|
||||
|
||||
if ( !validToOneRef ) {
|
||||
throw new SemanticException(
|
||||
String.format(
|
||||
Locale.ROOT,
|
||||
"`%s` used in `fk()` only supported for to-one mappings, but found `%s`",
|
||||
sqmPath.getNavigablePath().getFullPath(),
|
||||
toOneReference
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
return new SqmFkExpression( (SqmEntityValuedSimplePath<?>) sqmPath, creationContext.getNodeBuilder() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmMapEntryReference<?, ?> visitMapEntrySelection(HqlParser.MapEntrySelectionContext ctx) {
|
||||
return new SqmMapEntryReference<>(
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
|
|||
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
|
||||
|
@ -147,6 +148,8 @@ public interface SemanticQueryWalker<T> {
|
|||
|
||||
T visitPluralValuedPath(SqmPluralValuedSimplePath<?> path);
|
||||
|
||||
T visitFkExpression(SqmFkExpression<?> fkExpression);
|
||||
|
||||
T visitSelfInterpretingSqmPath(SelfInterpretingSqmPath<?> sqmPath);
|
||||
|
||||
T visitIndexedPluralAccessPath(SqmIndexedCollectionAccessPath<?> path);
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
|
|||
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
|
||||
|
@ -614,6 +615,13 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitFkExpression(SqmFkExpression<?> fkExpression) {
|
||||
logWithIndentation( "-> [fk-ref] - `%s`", fkExpression.getToOnePath().getNavigablePath().getFullPath() );
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitSelfInterpretingSqmPath(SelfInterpretingSqmPath<?> sqmPath) {
|
||||
logWithIndentation( "-> [self-interpreting-path] - `%s`", sqmPath.getNavigablePath().getFullPath() );
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.hibernate.query.sqm.tree.domain.SqmBasicValuedSimplePath;
|
|||
import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
|
||||
|
@ -309,6 +310,11 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
|
|||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitFkExpression(SqmFkExpression<?> fkExpression) {
|
||||
return fkExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitSelfInterpretingSqmPath(SelfInterpretingSqmPath<?> path) {
|
||||
return path;
|
||||
|
|
|
@ -162,6 +162,7 @@ import org.hibernate.query.sqm.tree.domain.SqmCorrelation;
|
|||
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
|
||||
|
@ -384,6 +385,7 @@ import static org.hibernate.query.sqm.TemporalUnit.EPOCH;
|
|||
import static org.hibernate.query.sqm.TemporalUnit.NATIVE;
|
||||
import static org.hibernate.query.sqm.TemporalUnit.SECOND;
|
||||
import static org.hibernate.query.sqm.UnaryArithmeticOperator.UNARY_MINUS;
|
||||
import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnReferenceKey;
|
||||
import static org.hibernate.type.spi.TypeConfiguration.isDuration;
|
||||
|
||||
/**
|
||||
|
@ -3261,7 +3263,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
);
|
||||
|
||||
final Expression expression = getSqlExpressionResolver().resolveSqlExpression(
|
||||
SqlExpressionResolver.createColumnReferenceKey(
|
||||
createColumnReferenceKey(
|
||||
tableReference,
|
||||
mapping.getSelectionExpression()
|
||||
),
|
||||
|
@ -3449,6 +3451,51 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
|
|||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitFkExpression(SqmFkExpression<?> fkExpression) {
|
||||
final EntityValuedPathInterpretation<?> toOneInterpretation = (EntityValuedPathInterpretation<?>) visitEntityValuedPath( fkExpression.getToOnePath() );
|
||||
assert toOneInterpretation.getExpressionType() instanceof ToOneAttributeMapping;
|
||||
|
||||
final ToOneAttributeMapping toOneMapping = (ToOneAttributeMapping) toOneInterpretation.getExpressionType();
|
||||
final ForeignKeyDescriptor fkDescriptor = toOneMapping.getForeignKeyDescriptor();
|
||||
final TableGroup tableGroup = toOneInterpretation.getTableGroup();
|
||||
final TableReference tableReference = tableGroup.resolveTableReference( fkDescriptor.getKeyTable() );
|
||||
|
||||
final ModelPart fkKeyPart = fkDescriptor.getKeyPart();
|
||||
if ( fkKeyPart instanceof BasicValuedModelPart ) {
|
||||
final BasicValuedModelPart basicFkPart = (BasicValuedModelPart) fkKeyPart;
|
||||
|
||||
return getSqlExpressionResolver().resolveSqlExpression(
|
||||
createColumnReferenceKey( tableReference, basicFkPart.getSelectionExpression() ),
|
||||
(sqlAstProcessingState) -> new ColumnReference(
|
||||
tableReference,
|
||||
basicFkPart,
|
||||
creationContext.getSessionFactory()
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
assert fkKeyPart instanceof EmbeddableValuedModelPart;
|
||||
final EmbeddableValuedModelPart compositeFkPart = (EmbeddableValuedModelPart) fkKeyPart;
|
||||
final List<JdbcMapping> jdbcMappings = compositeFkPart.getJdbcMappings();
|
||||
final List<Expression> tupleElements = new ArrayList<>( jdbcMappings.size() );
|
||||
compositeFkPart.forEachSelectable( (position, selectable) -> {
|
||||
tupleElements.add(
|
||||
getSqlExpressionResolver().resolveSqlExpression(
|
||||
createColumnReferenceKey( tableReference, selectable.getSelectionExpression() ),
|
||||
(sqlAstProcessingState) -> new ColumnReference(
|
||||
tableReference,
|
||||
selectable,
|
||||
creationContext.getSessionFactory()
|
||||
)
|
||||
)
|
||||
);
|
||||
} );
|
||||
|
||||
return new SqlTuple( tupleElements, compositeFkPart );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitSelfInterpretingSqmPath(SelfInterpretingSqmPath<?> sqmPath) {
|
||||
return prepareReusablePath(
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.tree.domain;
|
||||
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.SemanticQueryWalker;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.query.sqm.tree.expression.AbstractSqmExpression;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
|
||||
/**
|
||||
* Reference to the key-side (as opposed to the target-side) of the
|
||||
* foreign-key of a to-one association.
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SqmFkExpression<T> extends AbstractSqmExpression<T> {
|
||||
private final SqmEntityValuedSimplePath<?> toOnePath;
|
||||
|
||||
public SqmFkExpression(SqmEntityValuedSimplePath<?> toOnePath, NodeBuilder criteriaBuilder) {
|
||||
//noinspection unchecked
|
||||
super( (SqmExpressible<? extends T>) toOnePath.getNodeType().getIdType(), criteriaBuilder );
|
||||
this.toOnePath = toOnePath;
|
||||
}
|
||||
|
||||
public SqmEntityValuedSimplePath<?> getToOnePath() {
|
||||
return toOnePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X accept(SemanticQueryWalker<X> walker) {
|
||||
return walker.visitFkExpression( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendHqlString(StringBuilder sb) {
|
||||
sb.append( "fk(" );
|
||||
toOnePath.appendHqlString( sb );
|
||||
sb.append( ')' );
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqmExpression<T> copy(SqmCopyContext context) {
|
||||
final SqmFkExpression<T> existing = context.getCopy( this );
|
||||
if ( existing != null ) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
return context.registerCopy(
|
||||
this,
|
||||
new SqmFkExpression<T>( toOnePath.copy( context ), nodeBuilder() )
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* 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.notfound;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToOne;
|
||||
|
||||
import org.hibernate.annotations.NotFound;
|
||||
import org.hibernate.annotations.NotFoundAction;
|
||||
import org.hibernate.query.SemanticException;
|
||||
import org.hibernate.query.sqm.ParsingException;
|
||||
|
||||
import org.hibernate.testing.jdbc.SQLStatementInspector;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
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.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for the new `{fk}` HQL token
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
@DomainModel( annotatedClasses = { FkRefTests.Coin.class, FkRefTests.Currency.class } )
|
||||
@SessionFactory( useCollectingStatementInspector = true )
|
||||
public class FkRefTests {
|
||||
|
||||
@Test
|
||||
@JiraKey( "HHH-15099" )
|
||||
public void testSimplePredicateUse(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
// there is a Coin which has a currency_fk = 1
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where fk(c.currency) = 1";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
assertThat( coins ).hasSize( 1 );
|
||||
assertThat( coins.get( 0 ) ).isNotNull();
|
||||
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
||||
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
||||
} );
|
||||
|
||||
statementInspector.clear();
|
||||
|
||||
// However, the "matching" Currency does not exist
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where c.currency.id = 1";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
assertThat( coins ).hasSize( 0 );
|
||||
} );
|
||||
|
||||
statementInspector.clear();
|
||||
|
||||
// check using `currency` as a naked "property-ref"
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where fk(currency) = 1";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
assertThat( coins ).hasSize( 1 );
|
||||
assertThat( coins.get( 0 ) ).isNotNull();
|
||||
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
||||
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Baseline test for {@link #testNullnessPredicateUse}. Here we use the
|
||||
* normal "target" reference, which for a not-found mapping should trigger
|
||||
* a join to the association table and use the fk-target column
|
||||
*/
|
||||
@Test
|
||||
@JiraKey( "HHH-15099" )
|
||||
public void testNullnessPredicateUseBaseline(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
// there is one Coin (id=3) which has a null currency_fk, however its
|
||||
// target is missing (broken "fk"). this should return no results
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where c.currency.id is null";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
assertThat( coins ).hasSize( 0 );
|
||||
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " left " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " cross " );
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* It is not ideal that we render the join for these. Need to come back and address that
|
||||
*/
|
||||
@Test
|
||||
@JiraKey( "HHH-15099" )
|
||||
public void testNullnessPredicateUse(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
// there is one Coin (id=3) which has a null currency_fk
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where fk(c.currency) is null";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
assertThat( coins ).hasSize( 1 );
|
||||
assertThat( coins.get( 0 ) ).isNotNull();
|
||||
assertThat( coins.get( 0 ).getId() ).isEqualTo( 3 );
|
||||
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
||||
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
||||
} );
|
||||
|
||||
statementInspector.clear();
|
||||
|
||||
// check using `currency` as a naked "property-ref"
|
||||
scope.inTransaction( (session) -> {
|
||||
final String hql = "select c from Coin c where fk(currency) is null";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
assertThat( coins ).hasSize( 1 );
|
||||
assertThat( coins.get( 0 ) ).isNotNull();
|
||||
assertThat( coins.get( 0 ).getId() ).isEqualTo( 3 );
|
||||
assertThat( coins.get( 0 ).getCurrency() ).isNull();
|
||||
|
||||
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
|
||||
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey( "HHH-15099" )
|
||||
public void testFkRefDereferenceNotAllowed(SessionFactoryScope scope) {
|
||||
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
|
||||
statementInspector.clear();
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
try {
|
||||
final String hql = "select c from Coin c where fk(c.currency).something";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertThat( expected.getCause() ).isInstanceOf( ParsingException.class );
|
||||
}
|
||||
} );
|
||||
|
||||
scope.inTransaction( (session) -> {
|
||||
try {
|
||||
final String hql = "select c from Coin c where fk(currency).something";
|
||||
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList();
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertThat( expected.getCause() ).isInstanceOf( ParsingException.class );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void prepareTestData(SessionFactoryScope scope) {
|
||||
scope.inTransaction( (session) -> {
|
||||
Currency euro = new Currency( 1, "Euro" );
|
||||
Coin fiveC = new Coin( 1, "Five cents", euro );
|
||||
session.persist( euro );
|
||||
session.persist( fiveC );
|
||||
|
||||
Currency usd = new Currency( 2, "USD" );
|
||||
Coin penny = new Coin( 2, "Penny", usd );
|
||||
session.persist( usd );
|
||||
session.persist( penny );
|
||||
|
||||
Coin noCurrency = new Coin( 3, "N/A", null );
|
||||
session.persist( noCurrency );
|
||||
} );
|
||||
|
||||
// scope.inTransaction( (session) -> {
|
||||
// session.createQuery( "delete Currency where id = 1" ).executeUpdate();
|
||||
// } );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanupTest(SessionFactoryScope scope) throws Exception {
|
||||
scope.inTransaction( (session) -> {
|
||||
session.createMutationQuery( "delete Coin" ).executeUpdate();
|
||||
session.createMutationQuery( "delete Currency" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
|
||||
@Entity(name = "Coin")
|
||||
public static class Coin {
|
||||
private Integer id;
|
||||
private String name;
|
||||
private Currency currency;
|
||||
|
||||
public Coin() {
|
||||
}
|
||||
|
||||
public Coin(Integer id, String name, Currency currency) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
@Id
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@NotFound(action = NotFoundAction.IGNORE)
|
||||
@JoinColumn( name = "currency_fk" )
|
||||
public Currency getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
public void setCurrency(Currency currency) {
|
||||
this.currency = currency;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Currency")
|
||||
public static class Currency implements Serializable {
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
public Currency() {
|
||||
}
|
||||
|
||||
public Currency(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Id
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue