HHH-18066 Support de-referencing function invocation with dot and bracket syntax

This commit is contained in:
Christian Beikov 2024-05-03 14:45:47 +02:00
parent 940c898ecf
commit 2932933c43
15 changed files with 603 additions and 76 deletions

View File

@ -430,12 +430,17 @@ pathContinuation
* * VALUE( path ) * * VALUE( path )
* * KEY( path ) * * KEY( path )
* * path[ selector ] * * path[ selector ]
* * ARRAY_GET( embeddableArrayPath, index ).path
* * COALESCE( array1, array2 )[ selector ].path
*/ */
syntacticDomainPath syntacticDomainPath
: treatedNavigablePath : treatedNavigablePath
| collectionValueNavigablePath | collectionValueNavigablePath
| mapKeyNavigablePath | mapKeyNavigablePath
| simplePath indexedPathAccessFragment | simplePath indexedPathAccessFragment
| toOneFkReference
| function pathContinuation
| function indexedPathAccessFragment pathContinuation?
; ;
/** /**
@ -732,7 +737,6 @@ primaryExpression
| entityIdReference # EntityIdExpression | entityIdReference # EntityIdExpression
| entityVersionReference # EntityVersionExpression | entityVersionReference # EntityVersionExpression
| entityNaturalIdReference # EntityNaturalIdExpression | entityNaturalIdReference # EntityNaturalIdExpression
| toOneFkReference # ToOneFkExpression
| syntacticDomainPath pathContinuation? # SyntacticPathExpression | syntacticDomainPath pathContinuation? # SyntacticPathExpression
| function # FunctionExpression | function # FunctionExpression
| generalPathFragment # GeneralPathExpression | generalPathFragment # GeneralPathExpression

View File

@ -16,6 +16,7 @@ import org.hibernate.metamodel.mapping.MappingModelExpressible;
import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.query.ReturnableType; import org.hibernate.query.ReturnableType;
import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver; import org.hibernate.query.sqm.produce.function.FunctionReturnTypeResolver;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.sql.ast.tree.SqlAstNode; import org.hibernate.sql.ast.tree.SqlAstNode;
import org.hibernate.type.BasicType; import org.hibernate.type.BasicType;
@ -25,6 +26,8 @@ import org.hibernate.type.descriptor.jdbc.DelegatingJdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.spi.TypeConfiguration; import org.hibernate.type.spi.TypeConfiguration;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* A {@link FunctionReturnTypeResolver} that resolves a JSON encoded array type based on the arguments, * A {@link FunctionReturnTypeResolver} that resolves a JSON encoded array type based on the arguments,
* which are supposed to be of the element type. The inferred type and implied type have precedence though. * which are supposed to be of the element type. The inferred type and implied type have precedence though.
@ -39,16 +42,24 @@ public class JsonArrayViaElementArgumentReturnTypeResolver implements FunctionRe
@Override @Override
public ReturnableType<?> resolveFunctionReturnType( public ReturnableType<?> resolveFunctionReturnType(
ReturnableType<?> impliedType, ReturnableType<?> impliedType,
Supplier<MappingModelExpressible<?>> inferredTypeSupplier, @Nullable SqmToSqlAstConverter converter,
List<? extends SqmTypedNode<?>> arguments, List<? extends SqmTypedNode<?>> arguments,
TypeConfiguration typeConfiguration) { TypeConfiguration typeConfiguration) {
final MappingModelExpressible<?> inferredType = inferredTypeSupplier.get(); if ( converter != null ) {
if ( inferredType != null ) { if ( converter.isInTypeInference() ) {
if ( inferredType instanceof ReturnableType<?> ) { // Don't default to a Json array when in type inference mode.
return (ReturnableType<?>) inferredType; // Comparing e.g. `array() = (select array_agg() ...)` will trigger this resolver
// while inferring the type for `array()`, which we want to avoid.
return null;
} }
else if ( inferredType instanceof BasicValuedMapping ) { final MappingModelExpressible<?> inferredType = converter.resolveFunctionImpliedReturnType();
return (ReturnableType<?>) ( (BasicValuedMapping) inferredType ).getJdbcMapping(); if ( inferredType != null ) {
if ( inferredType instanceof ReturnableType<?> ) {
return (ReturnableType<?>) inferredType;
}
else if ( inferredType instanceof BasicValuedMapping ) {
return (ReturnableType<?>) ( (BasicValuedMapping) inferredType ).getJdbcMapping();
}
} }
} }
if ( impliedType != null ) { if ( impliedType != null ) {

View File

@ -33,6 +33,8 @@ import org.hibernate.type.descriptor.java.EnumJavaType;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Expression;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry; import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
/** /**
@ -41,7 +43,7 @@ import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
public class FullyQualifiedReflectivePathTerminal public class FullyQualifiedReflectivePathTerminal
extends FullyQualifiedReflectivePath extends FullyQualifiedReflectivePath
implements SqmExpression { implements SqmExpression {
private final SqmExpressible expressibleType; private final @Nullable SqmExpressible expressibleType;
private final SqmCreationState creationState; private final SqmCreationState creationState;
private final Function<SemanticQueryWalker,?> handler; private final Function<SemanticQueryWalker,?> handler;
@ -136,7 +138,7 @@ public class FullyQualifiedReflectivePathTerminal
} }
@Override @Override
public SqmExpressible getNodeType() { public @Nullable SqmExpressible getNodeType() {
return expressibleType; return expressibleType;
} }
@ -147,12 +149,12 @@ public class FullyQualifiedReflectivePathTerminal
@Override @Override
public JavaType getJavaTypeDescriptor() { public JavaType getJavaTypeDescriptor() {
return expressibleType.getExpressibleJavaType(); return expressibleType == null ? null : expressibleType.getExpressibleJavaType();
} }
@Override @Override
public void applyInferableType(SqmExpressible type) { public void applyInferableType(@Nullable SqmExpressible type) {
} }
@Override @Override

View File

@ -1869,7 +1869,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
} }
); );
try { try {
part = (SemanticPathPart) ctx.pathContinuation().accept( this ); part = (SemanticPathPart) ctx.pathContinuation().simplePath().accept( this );
} }
finally { finally {
dotIdentifierConsumerStack.pop(); dotIdentifierConsumerStack.pop();
@ -2965,11 +2965,11 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath() throw new FunctionArgumentException( "Argument '" + sqmPath.getNavigablePath()
+ "' of 'naturalid()' function does not resolve to an entity type" ); + "' of 'naturalid()' function does not resolve to an entity type" );
} }
//
@Override // @Override
public Object visitToOneFkExpression(HqlParser.ToOneFkExpressionContext ctx) { // public Object visitToOneFkExpression(HqlParser.ToOneFkExpressionContext ctx) {
return visitToOneFkReference( (HqlParser.ToOneFkReferenceContext) ctx.getChild( 0 ) ); // return visitToOneFkReference( (HqlParser.ToOneFkReferenceContext) ctx.getChild( 0 ) );
} // }
@Override @Override
public SqmFkExpression<?> visitToOneFkReference(HqlParser.ToOneFkReferenceContext ctx) { public SqmFkExpression<?> visitToOneFkReference(HqlParser.ToOneFkReferenceContext ctx) {
@ -2990,7 +2990,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
} }
return new SqmFkExpression<>( (SqmEntityValuedSimplePath<?>) sqmPath, creationContext.getNodeBuilder() ); return new SqmFkExpression<>( (SqmEntityValuedSimplePath<?>) sqmPath );
} }
@Override @Override
@ -5160,25 +5160,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
final HqlParser.SyntacticDomainPathContext syntacticDomainPath = ctx.syntacticDomainPath(); final HqlParser.SyntacticDomainPathContext syntacticDomainPath = ctx.syntacticDomainPath();
final HqlParser.GeneralPathFragmentContext generalPathFragment = ctx.generalPathFragment(); final HqlParser.GeneralPathFragmentContext generalPathFragment = ctx.generalPathFragment();
if ( syntacticDomainPath != null ) { if ( syntacticDomainPath != null ) {
final SemanticPathPart syntacticNavigablePathResult = return visitPathContinuation( visitSyntacticDomainPath( syntacticDomainPath ), ctx.pathContinuation() );
visitSyntacticDomainPath(syntacticDomainPath);
final HqlParser.PathContinuationContext pathContinuation = ctx.pathContinuation();
if ( pathContinuation != null ) {
dotIdentifierConsumerStack.push(
new BasicDotIdentifierConsumer( syntacticNavigablePathResult, this ) {
@Override
protected void reset() {
}
}
);
try {
return (SemanticPathPart) pathContinuation.accept( this );
}
finally {
dotIdentifierConsumerStack.pop();
}
}
return syntacticNavigablePathResult;
} }
else if (generalPathFragment != null) { else if (generalPathFragment != null) {
return (SemanticPathPart) generalPathFragment.accept(this); return (SemanticPathPart) generalPathFragment.accept(this);
@ -5190,7 +5172,7 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
@Override @Override
public SemanticPathPart visitGeneralPathFragment(HqlParser.GeneralPathFragmentContext ctx) { public SemanticPathPart visitGeneralPathFragment(HqlParser.GeneralPathFragmentContext ctx) {
return visitIndexedPathAccessFragment( ctx.simplePath(), ctx.indexedPathAccessFragment() ); return visitIndexedPathAccessFragment( visitSimplePath( ctx.simplePath() ), ctx.indexedPathAccessFragment() );
} }
@Override @Override
@ -5204,8 +5186,20 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
else if ( ctx.mapKeyNavigablePath() != null ) { else if ( ctx.mapKeyNavigablePath() != null ) {
return visitMapKeyNavigablePath( ctx.mapKeyNavigablePath() ); return visitMapKeyNavigablePath( ctx.mapKeyNavigablePath() );
} }
else if ( ctx.toOneFkReference() != null ) {
return visitToOneFkReference( ctx.toOneFkReference() );
}
else if ( ctx.function() != null ) {
return visitPathContinuation(
visitIndexedPathAccessFragment(
(SemanticPathPart) visitFunction( ctx.function() ),
ctx.indexedPathAccessFragment()
),
ctx.pathContinuation()
);
}
else if ( ctx.simplePath() != null && ctx.indexedPathAccessFragment() != null ) { else if ( ctx.simplePath() != null && ctx.indexedPathAccessFragment() != null ) {
return visitIndexedPathAccessFragment( ctx.simplePath(), ctx.indexedPathAccessFragment() ); return visitIndexedPathAccessFragment( visitSimplePath( ctx.simplePath() ), ctx.indexedPathAccessFragment() );
} }
else { else {
throw new ParsingException( "Illegal domain path '" + ctx.getText() + "'" ); throw new ParsingException( "Illegal domain path '" + ctx.getText() + "'" );
@ -5213,10 +5207,8 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
} }
private SemanticPathPart visitIndexedPathAccessFragment( private SemanticPathPart visitIndexedPathAccessFragment(
HqlParser.SimplePathContext ctx, SemanticPathPart pathPart,
HqlParser.IndexedPathAccessFragmentContext idxCtx) { HqlParser.IndexedPathAccessFragmentContext idxCtx) {
final SemanticPathPart pathPart = visitSimplePath( ctx );
if ( idxCtx == null ) { if ( idxCtx == null ) {
return pathPart; return pathPart;
} }
@ -5243,6 +5235,27 @@ public class SemanticQueryBuilder<R> extends HqlParserBaseVisitor<Object> implem
return indexedPath; return indexedPath;
} }
private SemanticPathPart visitPathContinuation(
SemanticPathPart pathPart,
HqlParser.PathContinuationContext pathContinuation) {
if ( pathContinuation == null ) {
return pathPart;
}
dotIdentifierConsumerStack.push(
new BasicDotIdentifierConsumer( pathPart, this ) {
@Override
protected void reset() {
}
}
);
try {
return (SemanticPathPart) pathContinuation.simplePath().accept( this );
}
finally {
dotIdentifierConsumerStack.pop();
}
}
@Override @Override
public SemanticPathPart visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext idxCtx) { public SemanticPathPart visitIndexedPathAccessFragment(HqlParser.IndexedPathAccessFragmentContext idxCtx) {
throw new UnsupportedOperationException( "Should be handled by #visitIndexedPathAccessFragment" ); throw new UnsupportedOperationException( "Should be handled by #visitIndexedPathAccessFragment" );

View File

@ -33,6 +33,7 @@ import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmFkExpression; import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
import org.hibernate.query.sqm.tree.domain.SqmFunctionPath;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath; import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmListJoin; import org.hibernate.query.sqm.tree.domain.SqmListJoin;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference; import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
@ -245,6 +246,8 @@ public interface SemanticQueryWalker<T> {
T visitIndexAggregateFunction(SqmIndexAggregateFunction<?> path); T visitIndexAggregateFunction(SqmIndexAggregateFunction<?> path);
T visitFunctionPath(SqmFunctionPath<?> functionPath);
T visitTreatedPath(SqmTreatedPath<?, ?> sqmTreatedPath); T visitTreatedPath(SqmTreatedPath<?, ?> sqmTreatedPath);
T visitCorrelation(SqmCorrelation<?, ?> correlation); T visitCorrelation(SqmCorrelation<?, ?> correlation);
@ -411,5 +414,4 @@ public interface SemanticQueryWalker<T> {
T visitMapEntryFunction(SqmMapEntryReference<?, ?> function); T visitMapEntryFunction(SqmMapEntryReference<?, ?> function);
T visitFullyQualifiedClass(Class<?> namedClass); T visitFullyQualifiedClass(Class<?> namedClass);
} }

View File

@ -26,6 +26,7 @@ import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmFkExpression; import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
import org.hibernate.query.sqm.tree.domain.SqmFunctionPath;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath; import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference; import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction; import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
@ -1080,6 +1081,11 @@ public class SqmTreePrinter implements SemanticQueryWalker<Object> {
return null; return null;
} }
@Override
public Object visitFunctionPath(SqmFunctionPath<?> functionPath) {
return null;
}
@Override @Override
public Object visitLiteral(SqmLiteral literal) { public Object visitLiteral(SqmLiteral literal) {
return null; return null;

View File

@ -25,6 +25,7 @@ import org.hibernate.query.sqm.tree.domain.SqmDerivedRoot;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmFkExpression; import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
import org.hibernate.query.sqm.tree.domain.SqmFunctionPath;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath; import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference; import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction; import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
@ -488,6 +489,12 @@ public abstract class BaseSemanticQueryWalker implements SemanticQueryWalker<Obj
return path; return path;
} }
@Override
public Object visitFunctionPath(SqmFunctionPath<?> functionPath) {
visitFunction( functionPath.getFunction() );
return functionPath;
}
@Override @Override
public Object visitCorrelation(SqmCorrelation<?, ?> correlation) { public Object visitCorrelation(SqmCorrelation<?, ?> correlation) {
return correlation; return correlation;

View File

@ -121,6 +121,7 @@ import org.hibernate.query.sqm.UnaryArithmeticOperator;
import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor; import org.hibernate.query.sqm.function.AbstractSqmSelfRenderingFunctionDescriptor;
import org.hibernate.query.sqm.function.SelfRenderingAggregateFunctionSqlAstExpression; import org.hibernate.query.sqm.function.SelfRenderingAggregateFunctionSqlAstExpression;
import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression; import org.hibernate.query.sqm.function.SelfRenderingFunctionSqlAstExpression;
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
import org.hibernate.query.sqm.internal.DomainParameterXref; import org.hibernate.query.sqm.internal.DomainParameterXref;
import org.hibernate.query.sqm.internal.SqmMappingModelHelper; import org.hibernate.query.sqm.internal.SqmMappingModelHelper;
import org.hibernate.query.sqm.mutation.internal.SqmInsertStrategyHelper; import org.hibernate.query.sqm.mutation.internal.SqmInsertStrategyHelper;
@ -164,6 +165,7 @@ import org.hibernate.query.sqm.tree.domain.SqmElementAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEmbeddedValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath; import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmFkExpression; import org.hibernate.query.sqm.tree.domain.SqmFkExpression;
import org.hibernate.query.sqm.tree.domain.SqmFunctionPath;
import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction; import org.hibernate.query.sqm.tree.domain.SqmIndexAggregateFunction;
import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath; import org.hibernate.query.sqm.tree.domain.SqmIndexedCollectionAccessPath;
import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference; import org.hibernate.query.sqm.tree.domain.SqmMapEntryReference;
@ -326,11 +328,14 @@ import org.hibernate.sql.ast.tree.expression.UnaryOperation;
import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral; import org.hibernate.sql.ast.tree.expression.UnparsedNumericLiteral;
import org.hibernate.sql.ast.tree.from.CorrelatedPluralTableGroup; import org.hibernate.sql.ast.tree.from.CorrelatedPluralTableGroup;
import org.hibernate.sql.ast.tree.from.CorrelatedTableGroup; import org.hibernate.sql.ast.tree.from.CorrelatedTableGroup;
import org.hibernate.sql.ast.tree.from.EmbeddableFunctionTableGroup;
import org.hibernate.sql.ast.tree.from.FromClause; import org.hibernate.sql.ast.tree.from.FromClause;
import org.hibernate.sql.ast.tree.from.FunctionTableGroup;
import org.hibernate.sql.ast.tree.from.NamedTableReference; import org.hibernate.sql.ast.tree.from.NamedTableReference;
import org.hibernate.sql.ast.tree.from.PluralTableGroup; import org.hibernate.sql.ast.tree.from.PluralTableGroup;
import org.hibernate.sql.ast.tree.from.QueryPartTableGroup; import org.hibernate.sql.ast.tree.from.QueryPartTableGroup;
import org.hibernate.sql.ast.tree.from.QueryPartTableReference; import org.hibernate.sql.ast.tree.from.QueryPartTableReference;
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
@ -391,6 +396,7 @@ import org.hibernate.type.SqlTypes;
import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.descriptor.converter.spi.BasicValueConverter;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.java.JavaTypeHelper; import org.hibernate.type.descriptor.java.JavaTypeHelper;
import org.hibernate.type.descriptor.jdbc.AggregateJdbcType;
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType; import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcType; import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators; import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
@ -3687,6 +3693,9 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
parentPath = sqmPath.getLhs(); parentPath = sqmPath.getLhs();
} }
if ( parentPath == null ) { if ( parentPath == null ) {
if ( sqmPath instanceof SqmFunctionPath<?> ) {
return visitFunctionPath( (SqmFunctionPath<?>) sqmPath );
}
return null; return null;
} }
final TableGroup parentTableGroup = getActualTableGroup( final TableGroup parentTableGroup = getActualTableGroup(
@ -4527,6 +4536,25 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return createMinOrMaxIndexOrElement( path, true, path.getFunctionName() ); return createMinOrMaxIndexOrElement( path, true, path.getFunctionName() );
} }
@Override
public TableGroup visitFunctionPath(SqmFunctionPath<?> functionPath) {
final NavigablePath navigablePath = functionPath.getNavigablePath();
TableGroup tableGroup = getFromClauseAccess().findTableGroup( navigablePath );
if ( tableGroup == null ) {
final Expression functionExpression = (Expression) functionPath.getFunction().accept( this );
final EmbeddableMappingType embeddableMappingType = ( (AggregateJdbcType) functionExpression.getExpressionType()
.getSingleJdbcMapping()
.getJdbcType() ).getEmbeddableMappingType();
tableGroup = new EmbeddableFunctionTableGroup(
navigablePath,
embeddableMappingType,
functionExpression
);
getFromClauseAccess().registerTableGroup( navigablePath, tableGroup );
}
return tableGroup;
}
@Override @Override
public Expression visitCorrelation(SqmCorrelation<?, ?> correlation) { public Expression visitCorrelation(SqmCorrelation<?, ?> correlation) {
final TableGroup resolved = getFromClauseAccess().findTableGroup( correlation.getNavigablePath() ); final TableGroup resolved = getFromClauseAccess().findTableGroup( correlation.getNavigablePath() );
@ -5371,7 +5399,7 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
@Override @Override
public MappingModelExpressible<?> resolveFunctionImpliedReturnType() { public MappingModelExpressible<?> resolveFunctionImpliedReturnType() {
if ( inImpliedResultTypeInference || functionImpliedResultTypeAccess == null ) { if ( inImpliedResultTypeInference || inTypeInference || functionImpliedResultTypeAccess == null ) {
return null; return null;
} }
inImpliedResultTypeInference = true; inImpliedResultTypeInference = true;

View File

@ -6,9 +6,15 @@
*/ */
package org.hibernate.query.sqm.tree.domain; package org.hibernate.query.sqm.tree.domain;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.model.domain.DomainType; import org.hibernate.metamodel.model.domain.DomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType; import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.UnknownPathException; import org.hibernate.query.sqm.UnknownPathException;
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.spi.NavigablePath; import org.hibernate.spi.NavigablePath;
import org.hibernate.query.PathException; import org.hibernate.query.PathException;
import org.hibernate.query.hql.spi.SqmCreationState; import org.hibernate.query.hql.spi.SqmCreationState;
@ -17,9 +23,12 @@ import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.SqmPathSource; import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.type.BasicPluralType;
import org.hibernate.type.descriptor.java.BasicJavaType; import org.hibernate.type.descriptor.java.BasicJavaType;
import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.JavaType;
import static java.util.Arrays.asList;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@ -86,6 +95,37 @@ public class SqmBasicValuedSimplePath<T>
); );
} }
@Override
public SqmPath<?> resolveIndexedAccess(
SqmExpression<?> selector,
boolean isTerminal,
SqmCreationState creationState) {
final SqmPathRegistry pathRegistry = creationState.getCurrentProcessingState().getPathRegistry();
final String alias = selector.toHqlString();
final NavigablePath navigablePath = getNavigablePath().getParent().append(
CollectionPart.Nature.ELEMENT.getName(),
alias
);
final SqmFrom<?, ?> indexedPath = pathRegistry.findFromByPath( navigablePath );
if ( indexedPath != null ) {
return indexedPath;
}
if ( !( getNodeType().getSqmPathType() instanceof BasicPluralType<?, ?> ) ) {
throw new UnsupportedOperationException( "Index access is only supported for basic plural types." );
}
final QueryEngine queryEngine = creationState.getCreationContext().getQueryEngine();
final SelfRenderingSqmFunction<?> result = queryEngine.getSqmFunctionRegistry()
.findFunctionDescriptor( "array_get" )
.generateSqmExpression(
asList( this, selector ),
null,
queryEngine
);
final SqmFunctionPath<Object> path = new SqmFunctionPath<>( result );
pathRegistry.register( path );
return path;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SqmPath // SqmPath

View File

@ -6,13 +6,19 @@
*/ */
package org.hibernate.query.sqm.tree.domain; package org.hibernate.query.sqm.tree.domain;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.IdentifiableDomainType; import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.sqm.NodeBuilder; import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SemanticQueryWalker; import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmExpressible; import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.produce.function.FunctionArgumentException;
import org.hibernate.query.sqm.tree.SqmCopyContext; import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.expression.AbstractSqmExpression; import org.hibernate.query.sqm.tree.expression.AbstractSqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmExpression; import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.spi.NavigablePath;
/** /**
* Reference to the key-side (as opposed to the target-side) of the * Reference to the key-side (as opposed to the target-side) of the
@ -20,13 +26,30 @@ import org.hibernate.query.sqm.tree.expression.SqmExpression;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class SqmFkExpression<T> extends AbstractSqmExpression<T> { public class SqmFkExpression<T> extends AbstractSqmPath<T> {
private final SqmEntityValuedSimplePath<?> toOnePath;
/**
* @deprecated Use {@link #SqmFkExpression(SqmEntityValuedSimplePath)} instead.
*/
@Deprecated(forRemoval = true)
public SqmFkExpression(SqmEntityValuedSimplePath<?> toOnePath, NodeBuilder criteriaBuilder) {
this( toOnePath );
}
public SqmFkExpression(SqmEntityValuedSimplePath<?> toOnePath) {
this( toOnePath.getNavigablePath().append( ForeignKeyDescriptor.PART_NAME ), toOnePath );
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public SqmFkExpression(SqmEntityValuedSimplePath<?> toOnePath, NodeBuilder criteriaBuilder) { private SqmFkExpression(
super( (SqmExpressible<? super T>) pathDomainType( toOnePath ).getIdType(), criteriaBuilder ); NavigablePath navigablePath,
this.toOnePath = toOnePath; SqmEntityValuedSimplePath<?> toOnePath) {
super(
navigablePath,
(SqmPathSource<T>) pathDomainType( toOnePath ).getIdentifierDescriptor(),
toOnePath,
toOnePath.nodeBuilder()
);
} }
private static IdentifiableDomainType<?> pathDomainType(SqmEntityValuedSimplePath<?> toOnePath) { private static IdentifiableDomainType<?> pathDomainType(SqmEntityValuedSimplePath<?> toOnePath) {
@ -34,7 +57,7 @@ public class SqmFkExpression<T> extends AbstractSqmExpression<T> {
} }
public SqmEntityValuedSimplePath<?> getToOnePath() { public SqmEntityValuedSimplePath<?> getToOnePath() {
return toOnePath; return (SqmEntityValuedSimplePath<?>) getLhs();
} }
@Override @Override
@ -45,20 +68,37 @@ public class SqmFkExpression<T> extends AbstractSqmExpression<T> {
@Override @Override
public void appendHqlString(StringBuilder sb) { public void appendHqlString(StringBuilder sb) {
sb.append( "fk(" ); sb.append( "fk(" );
toOnePath.appendHqlString( sb ); getLhs().appendHqlString( sb );
sb.append( ')' ); sb.append( ')' );
} }
@Override @Override
public SqmExpression<T> copy(SqmCopyContext context) { public SqmFkExpression<T> copy(SqmCopyContext context) {
final SqmFkExpression<T> existing = context.getCopy( this ); final SqmFkExpression<T> existing = context.getCopy( this );
if ( existing != null ) { if ( existing != null ) {
return existing; return existing;
} }
final SqmEntityValuedSimplePath<?> lhsCopy = (SqmEntityValuedSimplePath<?>) getLhs().copy( context );
return context.registerCopy( return context.registerCopy(
this, this,
new SqmFkExpression<T>( toOnePath.copy( context ), nodeBuilder() ) new SqmFkExpression<T>( getNavigablePathCopy( lhsCopy ), lhsCopy )
); );
} }
@Override
public <S extends T> SqmPath<S> treatAs(Class<S> treatJavaType) {
throw new FunctionArgumentException( "Fk paths cannot be TREAT-ed" );
}
@Override
public <S extends T> SqmPath<S> treatAs(EntityDomainType<S> treatTarget) {
throw new FunctionArgumentException( "Fk paths cannot be TREAT-ed" );
}
@Override
public SqmPath<?> resolvePathPart(String name, boolean isTerminal, SqmCreationState creationState) {
final SqmPath<?> sqmPath = get( name );
creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath );
return sqmPath;
}
} }

View File

@ -0,0 +1,150 @@
/*
* 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.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.model.domain.EmbeddableDomainType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.ListPersistentAttribute;
import org.hibernate.metamodel.model.domain.MapPersistentAttribute;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.internal.EmbeddedSqmPathSource;
import org.hibernate.query.NotIndexedCollectionException;
import org.hibernate.query.hql.spi.SqmCreationState;
import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.spi.QueryEngine;
import org.hibernate.query.sqm.SemanticQueryWalker;
import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.function.SelfRenderingSqmFunction;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.produce.function.FunctionArgumentException;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmFunction;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.spi.NavigablePath;
import org.hibernate.type.BasicPluralType;
import jakarta.persistence.metamodel.Bindable;
import static java.util.Arrays.asList;
public class SqmFunctionPath<T> extends AbstractSqmPath<T> {
private final SqmFunction<?> function;
public SqmFunctionPath(SqmFunction<?> function) {
this( new NavigablePath( function.toHqlString() ), function );
}
public SqmFunctionPath(NavigablePath navigablePath, SqmFunction<?> function) {
super(
navigablePath,
determinePathSource( navigablePath, function ),
null,
function.nodeBuilder()
);
this.function = function;
}
private static <X> SqmPathSource<X> determinePathSource(NavigablePath navigablePath, SqmFunction<?> function) {
//noinspection unchecked
final SqmExpressible<X> nodeType = (SqmExpressible<X>) function.getNodeType();
final EmbeddableDomainType<X> embeddableDomainType = function.nodeBuilder()
.getJpaMetamodel()
.embeddable( nodeType.getBindableJavaType() );
return new EmbeddedSqmPathSource<>(
navigablePath.getFullPath(),
null,
embeddableDomainType,
Bindable.BindableType.SINGULAR_ATTRIBUTE,
false
);
}
public SqmFunction<?> getFunction() {
return function;
}
@Override
public SqmFunctionPath<T> copy(SqmCopyContext context) {
final SqmFunctionPath<T> existing = context.getCopy( this );
if ( existing != null ) {
return existing;
}
final SqmFunctionPath<T> path = context.registerCopy(
this,
new SqmFunctionPath<>( getNavigablePath(), (SqmFunction<?>) function.copy( context ) )
);
copyTo( path, context );
return path;
}
@Override
public SqmPath<?> resolvePathPart(
String name,
boolean isTerminal,
SqmCreationState creationState) {
final SqmPath<?> sqmPath = get( name );
creationState.getProcessingStateStack().getCurrent().getPathRegistry().register( sqmPath );
return sqmPath;
}
@Override
public SqmPath<?> resolveIndexedAccess(
SqmExpression<?> selector,
boolean isTerminal,
SqmCreationState creationState) {
final SqmPathRegistry pathRegistry = creationState.getCurrentProcessingState().getPathRegistry();
final String alias = selector.toHqlString();
final NavigablePath navigablePath = getNavigablePath().getParent().append(
CollectionPart.Nature.ELEMENT.getName(),
alias
);
final SqmFrom<?, ?> indexedPath = pathRegistry.findFromByPath( navigablePath );
if ( indexedPath != null ) {
return indexedPath;
}
if ( !( getNodeType().getSqmPathType() instanceof BasicPluralType<?, ?> ) ) {
throw new UnsupportedOperationException( "Index access is only supported for basic plural types." );
}
final QueryEngine queryEngine = creationState.getCreationContext().getQueryEngine();
final SelfRenderingSqmFunction<?> result = queryEngine.getSqmFunctionRegistry()
.findFunctionDescriptor( "array_get" )
.generateSqmExpression(
asList( function, selector ),
null,
queryEngine
);
final SqmFunctionPath<Object> path = new SqmFunctionPath<>( result );
pathRegistry.register( path );
return path;
}
@Override
public <X> X accept(SemanticQueryWalker<X> walker) {
return walker.visitFunctionPath( this );
}
@Override
public void appendHqlString(StringBuilder sb) {
function.appendHqlString( sb );
}
@Override
public <S extends T> SqmPath<S> treatAs(Class<S> treatJavaType) {
throw new FunctionArgumentException( "Embeddable paths cannot be TREAT-ed" );
}
@Override
public <S extends T> SqmPath<S> treatAs(EntityDomainType<S> treatTarget) {
throw new FunctionArgumentException( "Embeddable paths cannot be TREAT-ed" );
}
}

View File

@ -17,9 +17,12 @@ import org.hibernate.query.sqm.SqmExpressible;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor; import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.SqmTypedNode; import org.hibernate.query.sqm.tree.SqmTypedNode;
import org.hibernate.query.sqm.tree.domain.SqmFunctionPath;
import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.Expression;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* A SQM function * A SQM function
* *
@ -37,7 +40,7 @@ public abstract class SqmFunction<T> extends AbstractSqmExpression<T>
public SqmFunction( public SqmFunction(
String functionName, String functionName,
SqmFunctionDescriptor functionDescriptor, SqmFunctionDescriptor functionDescriptor,
SqmExpressible<T> type, @Nullable SqmExpressible<T> type,
List<? extends SqmTypedNode<?>> arguments, List<? extends SqmTypedNode<?>> arguments,
NodeBuilder criteriaBuilder) { NodeBuilder criteriaBuilder) {
super( type, criteriaBuilder ); super( type, criteriaBuilder );
@ -175,13 +178,22 @@ public abstract class SqmFunction<T> extends AbstractSqmExpression<T>
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// SemanticPathPart // SemanticPathPart
private SqmFunctionPath<T> functionPath;
private SqmFunctionPath<T> getFunctionPath() {
SqmFunctionPath<T> path = functionPath;
if ( path == null ) {
path = functionPath = new SqmFunctionPath<T>( this );
}
return path;
}
@Override @Override
public SemanticPathPart resolvePathPart( public SemanticPathPart resolvePathPart(
String name, String name,
boolean isTerminal, boolean isTerminal,
SqmCreationState creationState) { SqmCreationState creationState) {
throw new UnsupportedOperationException(); return getFunctionPath().resolvePathPart( name, isTerminal, creationState );
} }
@Override @Override
@ -189,6 +201,6 @@ public abstract class SqmFunction<T> extends AbstractSqmExpression<T>
SqmExpression<?> selector, SqmExpression<?> selector,
boolean isTerminal, boolean isTerminal,
SqmCreationState creationState) { SqmCreationState creationState) {
throw new UnsupportedOperationException(); return getFunctionPath().resolveIndexedAccess( selector, isTerminal, creationState );
} }
} }

View File

@ -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.sql.ast.tree.from;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.tree.expression.Expression;
/**
* A table group for functions that produce embeddable typed results.
*/
public class EmbeddableFunctionTableGroup extends AbstractTableGroup {
private final EmbeddableFunctionTableReference tableReference;
public EmbeddableFunctionTableGroup(
NavigablePath navigablePath,
EmbeddableMappingType embeddableMappingType,
Expression expression) {
super(
false,
navigablePath,
embeddableMappingType,
null,
null,
null
);
this.tableReference = new EmbeddableFunctionTableReference( navigablePath, embeddableMappingType, expression );
}
@Override
public String getGroupAlias() {
return null;
}
@Override
public void applyAffectedTableNames(Consumer<String> nameCollector) {
tableReference.applyAffectedTableNames( nameCollector );
}
@Override
public TableReference getPrimaryTableReference() {
return tableReference;
}
@Override
public List<TableReferenceJoin> getTableReferenceJoins() {
return Collections.emptyList();
}
}

View File

@ -0,0 +1,104 @@
/*
* 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.sql.ast.tree.from;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.spi.NavigablePath;
import org.hibernate.sql.ast.SqlAstWalker;
import org.hibernate.sql.ast.tree.expression.Expression;
import static org.hibernate.internal.util.StringHelper.isEmpty;
/**
* A table reference for functions that produce embeddable typed results.
*/
public class EmbeddableFunctionTableReference extends AbstractTableReference {
private final String tableExpression;
private final Expression expression;
public EmbeddableFunctionTableReference(
NavigablePath navigablePath,
EmbeddableMappingType embeddableMappingType,
Expression expression) {
super( navigablePath.getFullPath(), false );
this.tableExpression = embeddableMappingType.getAggregateMapping().getContainingTableExpression();
this.expression = expression;
}
public Expression getExpression() {
return expression;
}
@Override
public void accept(SqlAstWalker sqlTreeWalker) {
expression.accept( sqlTreeWalker );
}
@Override
public List<String> getAffectedTableNames() {
return Collections.singletonList( tableExpression );
}
@Override
public boolean containsAffectedTableName(String requestedName) {
return isEmpty( requestedName ) || tableExpression.equals( requestedName );
}
@Override
public void applyAffectedTableNames(Consumer<String> nameCollector) {
nameCollector.accept( tableExpression );
}
@Override
public String getTableId() {
return tableExpression;
}
@Override
public Boolean visitAffectedTableNames(Function<String, Boolean> nameCollector) {
return nameCollector.apply( tableExpression );
}
@Override
public TableReference resolveTableReference(
NavigablePath navigablePath,
String tableExpression) {
if ( this.tableExpression.equals( tableExpression ) ) {
return this;
}
throw new UnknownTableReferenceException(
tableExpression,
String.format(
Locale.ROOT,
"Unable to determine TableReference (`%s`) for `%s`",
tableExpression,
navigablePath
)
);
}
@Override
public TableReference getTableReference(
NavigablePath navigablePath,
String tableExpression,
boolean resolve) {
return this.tableExpression.equals( tableExpression ) ? this : null;
}
@Override
public String toString() {
return identificationVariable;
}
}

View File

@ -12,6 +12,7 @@ import java.util.List;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import org.hibernate.query.SyntaxException; import org.hibernate.query.SyntaxException;
import org.hibernate.query.sqm.UnknownPathException;
import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.jdbc.SQLStatementInspector;
import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModel;
@ -22,10 +23,13 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import jakarta.persistence.Column;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne; import jakarta.persistence.OneToOne;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -35,13 +39,13 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@DomainModel( annotatedClasses = { FkRefTests.Coin.class, FkRefTests.Currency.class } ) @DomainModel( annotatedClasses = { FkRefTests.Coin.class, FkRefTests.Currency.class, FkRefTests.Exchange.class } )
@SessionFactory( useCollectingStatementInspector = true ) @SessionFactory( useCollectingStatementInspector = true )
@JiraKey( "HHH-15099" )
@JiraKey( "HHH-15106" )
public class FkRefTests { public class FkRefTests {
@Test @Test
@JiraKey( "HHH-15099" )
@JiraKey( "HHH-15106" )
public void testSimplePredicateUse(SessionFactoryScope scope) { public void testSimplePredicateUse(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear(); statementInspector.clear();
@ -88,8 +92,6 @@ public class FkRefTests {
* a join to the association table and use the fk-target column * a join to the association table and use the fk-target column
*/ */
@Test @Test
@JiraKey( "HHH-15099" )
@JiraKey( "HHH-15106" )
public void testNullnessPredicateUseBaseline(SessionFactoryScope scope) { public void testNullnessPredicateUseBaseline(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear(); statementInspector.clear();
@ -109,8 +111,6 @@ public class FkRefTests {
} }
@Test @Test
@JiraKey( "HHH-15099" )
@JiraKey( "HHH-15106" )
public void testNullnessPredicateUse1(SessionFactoryScope scope) { public void testNullnessPredicateUse1(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear(); statementInspector.clear();
@ -151,8 +151,6 @@ public class FkRefTests {
* the currency does not need to be fetched. So it works there * the currency does not need to be fetched. So it works there
*/ */
@Test @Test
@JiraKey( "HHH-15099" )
@JiraKey( "HHH-15106" )
public void testNullnessPredicateUse2(SessionFactoryScope scope) { public void testNullnessPredicateUse2(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear(); statementInspector.clear();
@ -185,33 +183,52 @@ public class FkRefTests {
} }
@Test @Test
@JiraKey( "HHH-15099" ) public void testFkRefDereferenceInvalid(SessionFactoryScope scope) {
@JiraKey( "HHH-15106" )
public void testFkRefDereferenceNotAllowed(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear(); statementInspector.clear();
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
try { try {
final String hql = "select c from Coin c where fk(c.currency).something"; final String hql = "select c from Coin c where fk(c.currency).something is not null";
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList(); session.createQuery( hql, Coin.class ).getResultList();
} }
catch (IllegalArgumentException expected) { catch (IllegalArgumentException expected) {
assertThat( expected.getCause() ).isInstanceOf( SyntaxException.class ); assertThat( expected.getCause() ).isInstanceOf( UnknownPathException.class );
} }
} ); } );
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
try { try {
final String hql = "select c from Coin c where fk(currency).something"; final String hql = "select c from Coin c where fk(currency).something is not null";
final List<Coin> coins = session.createQuery( hql, Coin.class ).getResultList(); session.createQuery( hql, Coin.class ).getResultList();
} }
catch (IllegalArgumentException expected) { catch (IllegalArgumentException expected) {
assertThat( expected.getCause() ).isInstanceOf( SyntaxException.class ); assertThat( expected.getCause() ).isInstanceOf( UnknownPathException.class );
} }
} ); } );
} }
@Test
public void testFkRefDereference(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear();
scope.inTransaction( (session) -> {
final String hql = "select c from Coin c where fk(c.exchange).name is not null";
session.createQuery( hql, Coin.class ).getResultList();
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
} );
statementInspector.clear();
scope.inTransaction( (session) -> {
final String hql = "select c from Coin c where fk(exchange).name is not null";
session.createQuery( hql, Coin.class ).getResultList();
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
} );
}
@BeforeEach @BeforeEach
public void prepareTestData(SessionFactoryScope scope) { public void prepareTestData(SessionFactoryScope scope) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
@ -247,6 +264,7 @@ public class FkRefTests {
private Integer id; private Integer id;
private String name; private String name;
private Currency currency; private Currency currency;
private Exchange exchange;
public Coin() { public Coin() {
} }
@ -284,6 +302,18 @@ public class FkRefTests {
public void setCurrency(Currency currency) { public void setCurrency(Currency currency) {
this.currency = currency; this.currency = currency;
} }
@ManyToOne(fetch = FetchType.LAZY)
@NotFound(action = NotFoundAction.IGNORE)
@JoinColumn(name = "exchange_country", referencedColumnName = "country_iso")
@JoinColumn(name = "exchange_name", referencedColumnName = "name")
public Exchange getExchange() {
return exchange;
}
public void setExchange(Exchange exchange) {
this.exchange = exchange;
}
} }
@Entity(name = "Currency") @Entity(name = "Currency")
@ -316,4 +346,23 @@ public class FkRefTests {
this.name = name; this.name = name;
} }
} }
@Entity(name = "Exchange")
public static class Exchange implements Serializable {
@EmbeddedId
private ExchangeId id;
private String description;
public Exchange() {
}
}
public static class ExchangeId implements Serializable {
private String name;
@Column(name = "country_iso")
private String countryIso;
public ExchangeId() {
}
}
} }