6 - SQM based on JPA type system

- moving SQM-specific tests from wip/6.0
This commit is contained in:
Steve Ebersole 2019-07-25 11:59:38 -05:00 committed by Andrea Boriero
parent 00da979e70
commit b101ffbf79
30 changed files with 1811 additions and 235 deletions

View File

@ -176,38 +176,46 @@ public class SimpleValueBinder {
else if ( ( !key && property.isAnnotationPresent( Temporal.class ) )
|| ( key && property.isAnnotationPresent( MapKeyTemporal.class ) ) ) {
boolean isDate;
if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, Date.class ) ) {
isDate = true;
}
else if ( buildingContext.getBootstrapContext().getReflectionManager().equals( returnedClassOrElement, Calendar.class ) ) {
isDate = false;
}
else {
throw new AnnotationException(
"@Temporal should only be set on a java.util.Date or java.util.Calendar property: "
+ StringHelper.qualify( persistentClassName, propertyName )
);
}
// we should be able to handle this using the Java type because it should denote a basic value
final Class<?> valueJavaType = buildingContext.getBootstrapContext().getReflectionManager().toClass(returnedClassOrElement );
final TemporalType temporalType = getTemporalType( property );
switch ( temporalType ) {
case DATE:
type = isDate ? "date" : "calendar_date";
break;
case TIME:
type = "time";
if ( !isDate ) {
if ( Date.class.isAssignableFrom( valueJavaType ) ) {
switch ( temporalType ) {
case DATE: {
type = "date";
break;
}
case TIME: {
type = "time";
break;
}
default: {
type = "timestamp";
break;
}
}
}
else if ( Calendar.class.isAssignableFrom( valueJavaType ) ) {
switch ( temporalType ) {
case DATE: {
type = "calendar_date";
break;
}
case TIME: {
throw new NotYetImplementedException(
"Calendar cannot persist TIME only"
+ StringHelper.qualify( persistentClassName, propertyName )
"Calendar cannot persist TIME only" + StringHelper.qualify( persistentClassName, propertyName )
);
}
break;
case TIMESTAMP:
type = isDate ? "timestamp" : "calendar";
break;
default:
throw new AssertionFailure( "Unknown temporal type: " + temporalType );
default: {
type = "calendar";
break;
}
}
}
else if ( java.time.temporal.Temporal.class.isAssignableFrom( valueJavaType ) ) {
type = valueJavaType.getName();
}
explicitType = type;
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.metamodel.model.domain.internal;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.BasicDomainType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.sqm.IllegalPathUsageException;
@ -17,7 +18,7 @@ import org.hibernate.query.sqm.tree.domain.SqmPath;
/**
* @author Steve Ebersole
*/
public class BasicSqmPathSource<J> extends AbstractSqmPathSource<J> {
public class BasicSqmPathSource<J> extends AbstractSqmPathSource<J> implements AllowableParameterType<J> {
@SuppressWarnings("WeakerAccess")
public BasicSqmPathSource(
String localPathName,

View File

@ -0,0 +1,240 @@
/*
* 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.hql.internal;
import java.lang.reflect.Field;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.query.hql.spi.DotIdentifierConsumer;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.sqm.ParsingException;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.produce.spi.SqmCreationContext;
import org.hibernate.query.sqm.produce.spi.SqmCreationState;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmEnumLiteral;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
import org.hibernate.query.sqm.tree.expression.SqmFieldLiteral;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry;
import org.jboss.logging.Logger;
/**
* @asciidoc
*
* DotIdentifierHandler used to interpret paths outside of any specific
* context. This is the handler used at the root of the handler stack.
*
* It can recognize any number of types of paths -
*
* * fully-qualified class names (entity or otherwise)
* * static field references, e.g. `MyClass.SOME_FIELD`
* * enum value references, e.g. `Sex.MALE`
* * navigable-path
* * others?
*
* @author Steve Ebersole
*/
public class BasicDotIdentifierConsumer implements DotIdentifierConsumer {
private static final Logger log = Logger.getLogger( BasicDotIdentifierConsumer.class );
private final SqmCreationState creationState;
private String pathSoFar;
private SemanticPathPart currentPart;
public BasicDotIdentifierConsumer(SqmCreationState creationState) {
this.creationState = creationState;
}
public BasicDotIdentifierConsumer(SemanticPathPart initialState, SqmCreationState creationState) {
this.currentPart = initialState;
this.creationState = creationState;
}
protected SqmCreationState getCreationState() {
return creationState;
}
@Override
public SemanticPathPart getConsumedPart() {
return currentPart;
}
@Override
public void consumeIdentifier(String identifier, boolean isBase, boolean isTerminal) {
if ( isBase ) {
// each time we start a new sequence we need to reset our state
reset();
}
if ( pathSoFar == null ) {
pathSoFar = identifier;
}
else {
pathSoFar += ( '.' + identifier );
}
log.tracef(
"BasicDotIdentifierHandler#consumeIdentifier( %s, %s, %s ) - %s",
identifier,
isBase,
isTerminal,
pathSoFar
);
currentPart = currentPart.resolvePathPart( identifier, isTerminal, creationState );
}
protected void reset() {
pathSoFar = null;
currentPart = createBasePart();
}
protected SemanticPathPart createBasePart() {
return new BaseLocalSequencePart();
}
public class BaseLocalSequencePart implements SemanticPathPart {
private boolean isBase = true;
@Override
public SemanticPathPart resolvePathPart(
String identifier,
boolean isTerminal,
SqmCreationState creationState) {
if ( isBase ) {
isBase = false;
final SqmPathRegistry sqmPathRegistry = creationState.getProcessingStateStack()
.getCurrent()
.getPathRegistry();
final SqmFrom pathRootByAlias = sqmPathRegistry.findFromByAlias( identifier );
if ( pathRootByAlias != null ) {
// identifier is an alias (identification variable)
validateAsRoot( pathRootByAlias );
if ( isTerminal ) {
return pathRootByAlias;
}
else {
return new DomainPathPart( pathRootByAlias );
}
}
final SqmFrom pathRootByExposedNavigable = sqmPathRegistry.findFromExposing( identifier );
if ( pathRootByExposedNavigable != null ) {
// identifier is an "unqualified attribute reference"
validateAsRoot( pathRootByExposedNavigable );
final SqmPathSource subPathSource = pathRootByExposedNavigable.getReferencedPathSource().findSubPathSource( identifier );
final SqmPath sqmPath = subPathSource.createSqmPath( pathRootByExposedNavigable, creationState );
if ( isTerminal ) {
return sqmPath;
}
else {
pathRootByExposedNavigable.registerImplicitJoinPath( sqmPath );
return new DomainPathPart( pathRootByAlias );
}
}
}
// at the moment, below this point we wait to resolve the sequence until we hit the terminal
//
// we could check for "intermediate resolution", but that comes with a performance hit. E.g., consider
//
// `org.hibernate.test.Sex.MALE`
//
// we could check `org` and then `org.hibernate` and then `org.hibernate.test` and then ... until
// we know it is a package, class or entity name. That gets expensive though. For now, plan on
// resolving these at the terminal
//
// todo (6.0) : finish this logic. and see above note in `! isTerminal` block
if ( !isTerminal ) {
return this;
}
// // see if it is a Class name...
// try {
// final Class<?> namedClass = creationState.getCreationContext()
// .getServiceRegistry()
// .getService( ClassLoaderService.class )
// .classForName( pathSoFar );
// if ( namedClass != null ) {
// return new
// }
// }
// catch (Exception ignore) {
// }
// see if it is a named field/enum reference
final int splitPosition = pathSoFar.lastIndexOf( '.' );
if ( splitPosition > 0 ) {
final String prefix = pathSoFar.substring( 0, splitPosition );
final String terminal = pathSoFar.substring( splitPosition + 1 );
try {
final SqmCreationContext creationContext = creationState.getCreationContext();
final Class<?> namedClass = creationContext
.getServiceRegistry()
.getService( ClassLoaderService.class )
.classForName( prefix );
if ( namedClass != null ) {
final JavaTypeDescriptorRegistry javaTypeDescriptorRegistry = creationContext.getJpaMetamodel()
.getTypeConfiguration()
.getJavaTypeDescriptorRegistry();
try {
final Field referencedField = namedClass.getDeclaredField( terminal );
if ( referencedField != null ) {
final JavaTypeDescriptor<?> fieldJtd = javaTypeDescriptorRegistry
.getDescriptor( referencedField.getType() );
//noinspection unchecked
return new SqmFieldLiteral( referencedField, fieldJtd, creationContext.getNodeBuilder() );
}
}
catch (Exception ignore) {
}
if ( namedClass.isEnum() ) {
//noinspection unchecked
final Enum referencedEnum = Enum.valueOf( (Class) namedClass, terminal );
//noinspection unchecked
return new SqmEnumLiteral(
referencedEnum,
(EnumJavaTypeDescriptor) javaTypeDescriptorRegistry.resolveDescriptor( namedClass ),
terminal,
creationContext.getNodeBuilder()
);
}
}
}
catch (Exception ignore) {
}
}
throw new ParsingException( "Could not interpret dot-ident : " + pathSoFar );
}
protected void validateAsRoot(SqmFrom pathRoot) {
}
@Override
public SqmPath resolveIndexedAccess(
SqmExpression selector,
boolean isTerminal,
SqmCreationState processingState) {
return currentPart.resolveIndexedAccess( selector, isTerminal, processingState );
}
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.hql.internal;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.produce.spi.SqmCreationState;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.expression.SqmExpression;
/**
* Specialized "intermediate" SemanticPathPart for processing domain model paths
*
* @author Steve Ebersole
*/
public class DomainPathPart implements SemanticPathPart {
private SqmPath<?> currentPath;
@SuppressWarnings("WeakerAccess")
public DomainPathPart(SqmPath<?> basePath) {
this.currentPath = basePath;
}
@Override
public SemanticPathPart resolvePathPart(
String name,
boolean isTerminal,
SqmCreationState creationState) {
final SqmPath<?> lhs = currentPath;
final SqmPathSource subPathSource = lhs.getReferencedPathSource().findSubPathSource( name );
//noinspection unchecked
currentPath = subPathSource.createSqmPath( lhs, creationState );
if ( isTerminal ) {
return currentPath;
}
else {
lhs.registerImplicitJoinPath( currentPath );
return this;
}
}
@Override
public SqmPath resolveIndexedAccess(
SqmExpression selector,
boolean isTerminal,
SqmCreationState creationState) {
throw new NotYetImplementedFor6Exception( getClass() );
}
}

View File

@ -0,0 +1,105 @@
/*
* 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.hql.internal;
import org.hibernate.query.SemanticException;
import org.hibernate.query.hql.spi.DotIdentifierConsumer;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.hql.spi.SqmPathRegistry;
import org.hibernate.query.sqm.SqmJoinable;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.produce.spi.SqmCreationProcessingState;
import org.hibernate.query.sqm.produce.spi.SqmCreationState;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmRoot;
/**
* Specialized "intermediate" SemanticPathPart for processing domain model paths
*
* @author Steve Ebersole
*/
public class QualifiedJoinPathConsumer implements DotIdentifierConsumer {
private final SqmCreationState creationState;
private final SqmRoot sqmRoot;
private final SqmJoinType joinType;
private final boolean fetch;
private final String alias;
private SqmFrom currentPath;
public QualifiedJoinPathConsumer(
SqmRoot<?> sqmRoot,
SqmJoinType joinType,
boolean fetch,
String alias,
SqmCreationState creationState) {
this.sqmRoot = sqmRoot;
this.joinType = joinType;
this.fetch = fetch;
this.alias = alias;
this.creationState = creationState;
}
@Override
public void consumeIdentifier(String identifier, boolean isBase, boolean isTerminal) {
if ( isBase ) {
assert currentPath == null;
this.currentPath = resolvePathBase( identifier, isTerminal, creationState );
}
else {
assert currentPath != null;
currentPath = createJoin( currentPath, identifier, isTerminal, creationState );
}
}
private SqmFrom resolvePathBase(String identifier, boolean isTerminal, SqmCreationState creationState) {
final SqmCreationProcessingState processingState = creationState.getCurrentProcessingState();
final SqmPathRegistry pathRegistry = processingState.getPathRegistry();
final SqmFrom pathRootByAlias = pathRegistry.findFromByAlias( identifier );
if ( pathRootByAlias != null ) {
// identifier is an alias (identification variable)
if ( isTerminal ) {
throw new SemanticException( "Cannot join to root : " + identifier );
}
return pathRootByAlias;
}
final SqmFrom pathRootByExposedNavigable = pathRegistry.findFromExposing( identifier );
if ( pathRootByExposedNavigable != null ) {
return createJoin( pathRootByExposedNavigable, identifier, isTerminal, creationState );
}
throw new SemanticException( "Could not determine how to resolve qualified join base : " + identifier );
}
private SqmFrom createJoin(SqmFrom lhs, String identifier, boolean isTerminal, SqmCreationState creationState) {
final SqmPathSource subPathSource = lhs.getReferencedPathSource().findSubPathSource( identifier );
final SqmAttributeJoin join = ( (SqmJoinable) subPathSource ).createSqmJoin(
lhs,
joinType,
isTerminal ? alias : null,
fetch,
creationState
);
lhs.addSqmJoin( join );
creationState.getCurrentProcessingState().getPathRegistry().register( join );
return join;
}
@Override
public SemanticPathPart getConsumedPart() {
return currentPath;
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.hql.internal;
import java.util.Locale;
import org.hibernate.query.SemanticException;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.sqm.produce.spi.SqmCreationState;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
/**
* Specialized consumer for processing domain model paths occurring as part
* of a join predicate
*
* @author Steve Ebersole
*/
public class QualifiedJoinPredicatePathConsumer extends BasicDotIdentifierConsumer {
private final SqmRoot sqmRoot;
private final SqmQualifiedJoin sqmJoin;
public QualifiedJoinPredicatePathConsumer(
SqmRoot<?> sqmRoot,
SqmQualifiedJoin sqmJoin,
SqmCreationState creationState) {
super( creationState );
this.sqmRoot = sqmRoot;
this.sqmJoin = sqmJoin;
}
@Override
protected SemanticPathPart createBasePart() {
return new BaseLocalSequencePart() {
@Override
protected void validateAsRoot(SqmFrom pathRoot) {
if ( pathRoot.findRoot() != sqmJoin.findRoot() ) {
throw new SemanticException(
String.format(
Locale.ROOT,
"SqmQualifiedJoin predicate referred to SqmRoot [`%s`] other than the join's root [`%s`]",
pathRoot.getNavigablePath().getFullPath(),
sqmJoin.getNavigablePath().getFullPath()
)
);
}
super.validateAsRoot( pathRoot );
}
};
}
}

View File

@ -40,6 +40,7 @@ import org.hibernate.query.SemanticException;
import org.hibernate.query.TrimSpec;
import org.hibernate.query.UnaryArithmeticOperator;
import org.hibernate.query.hql.HqlInterpretationException;
import org.hibernate.query.hql.spi.DotIdentifierConsumer;
import org.hibernate.query.hql.spi.SemanticPathPart;
import org.hibernate.query.sqm.LiteralNumberFormatException;
import org.hibernate.query.sqm.NodeBuilder;
@ -48,7 +49,12 @@ import org.hibernate.query.sqm.SqmExpressable;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.StrictJpaComplianceViolation;
import org.hibernate.query.sqm.UnknownEntityException;
import org.hibernate.query.sqm.produce.spi.SqmCreationProcessingState;
import org.hibernate.query.sqm.function.SqmCastTarget;
import org.hibernate.query.sqm.function.SqmDistinct;
import org.hibernate.query.sqm.function.SqmExtractUnit;
import org.hibernate.query.sqm.function.SqmStar;
import org.hibernate.query.sqm.function.SqmTrimSpecification;
import org.hibernate.query.sqm.internal.ParameterCollector;
import org.hibernate.query.sqm.produce.SqmTreeCreationLogger;
import org.hibernate.query.sqm.produce.function.SqmFunctionTemplate;
import org.hibernate.query.sqm.produce.function.spi.NamedSqmFunctionTemplate;
@ -59,6 +65,7 @@ import org.hibernate.query.sqm.produce.spi.ImplicitAliasGenerator;
import org.hibernate.query.sqm.produce.spi.ParameterDeclarationContext;
import org.hibernate.query.sqm.produce.spi.SqmCreationContext;
import org.hibernate.query.sqm.produce.spi.SqmCreationOptions;
import org.hibernate.query.sqm.produce.spi.SqmCreationProcessingState;
import org.hibernate.query.sqm.produce.spi.SqmCreationState;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.SqmStatement;
@ -86,11 +93,6 @@ import org.hibernate.query.sqm.tree.expression.SqmNamedParameter;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.expression.SqmPositionalParameter;
import org.hibernate.query.sqm.tree.expression.SqmUnaryOperation;
import org.hibernate.query.sqm.function.SqmCastTarget;
import org.hibernate.query.sqm.function.SqmDistinct;
import org.hibernate.query.sqm.function.SqmExtractUnit;
import org.hibernate.query.sqm.function.SqmStar;
import org.hibernate.query.sqm.function.SqmTrimSpecification;
import org.hibernate.query.sqm.tree.from.DowncastLocation;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmCrossJoin;
@ -101,7 +103,6 @@ import org.hibernate.query.sqm.tree.from.SqmJoin;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.insert.SqmInsertSelectStatement;
import org.hibernate.query.sqm.internal.ParameterCollector;
import org.hibernate.query.sqm.tree.predicate.SqmAndPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmEmptinessPredicate;
@ -167,7 +168,7 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
private final ImplicitAliasGenerator implicitAliasGenerator;
private final UniqueIdGenerator uidGenerator;
private final Stack<SemanticPathPart> semanticPathPartStack;
private final Stack<DotIdentifierConsumer> dotIdentifierConsumerStack;
private final Stack<TreatHandler> treatHandlerStack = new StandardStack<>( new TreatHandlerNormal() );
@ -188,7 +189,8 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
this.implicitAliasGenerator = new ImplicitAliasGenerator();
this.uidGenerator = new UniqueIdGenerator();
this.semanticPathPartStack = new StandardStack<>( new SemanticPathPartRoot() );
this.dotIdentifierConsumerStack = new StandardStack<>( new BasicDotIdentifierConsumer( this ) );
}
@Override
@ -962,14 +964,15 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
final String alias = visitIdentificationVariableDef( parserJoin.qualifiedJoinRhs().identificationVariableDef() );
semanticPathPartStack.push(
new SemanticPathPartQualifiedJoinPath(
dotIdentifierConsumerStack.push(
new QualifiedJoinPathConsumer(
sqmRoot,
joinType,
parserJoin.FETCH() != null,
alias
alias,
this
)
);
);
try {
final SqmQualifiedJoin join = (SqmQualifiedJoin) parserJoin.qualifiedJoinRhs().path().accept( this );
@ -995,23 +998,18 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
}
}
// IMPORTANT : register before processing the join-predicate so that handling the
// predicate has access to it...
processingStateStack.getCurrent().getPathRegistry().register( join );
if ( parserJoin.qualifiedJoinPredicate() != null ) {
semanticPathPartStack.push( new SemanticPathPartJoinPredicate( join ) );
dotIdentifierConsumerStack.push( new QualifiedJoinPredicatePathConsumer( sqmRoot, join, this ) );
try {
join.setJoinPredicate( (SqmPredicate) parserJoin.qualifiedJoinPredicate().predicate().accept( this ) );
}
finally {
semanticPathPartStack.pop();
dotIdentifierConsumerStack.pop();
}
}
}
finally {
semanticPathPartStack.pop();
dotIdentifierConsumerStack.pop();
}
}
@ -1024,20 +1022,22 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
protected void consumeJpaCollectionJoin(
HqlParser.JpaCollectionJoinContext ctx,
SqmRoot sqmRoot) {
semanticPathPartStack.push(
new SemanticPathPartQualifiedJoinPath(
dotIdentifierConsumerStack.push(
new QualifiedJoinPathConsumer(
sqmRoot,
// todo (6.0) : what kind of join is
SqmJoinType.LEFT,
false,
visitIdentificationVariableDef( ctx.identificationVariableDef() )
null,
this
)
);
try {
consumePluralAttributeReference( ctx.path() );
}
finally {
semanticPathPartStack.pop();
dotIdentifierConsumerStack.pop();
}
}
@ -2600,12 +2600,18 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
if ( ctx.syntacticDomainPath() != null ) {
final SemanticPathPart syntacticNavigablePathResult = visitSyntacticDomainPath( ctx.syntacticDomainPath() );
if ( ctx.pathContinuation() != null ) {
semanticPathPartStack.push( syntacticNavigablePathResult );
dotIdentifierConsumerStack.push(
new BasicDotIdentifierConsumer( syntacticNavigablePathResult, this ) {
@Override
protected void reset() {
}
}
);
try {
return (SemanticPathPart) ctx.pathContinuation().accept( this );
}
finally {
semanticPathPartStack.pop();
dotIdentifierConsumerStack.pop();
}
}
return syntacticNavigablePathResult;
@ -2646,31 +2652,59 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
}
// @Override
// public SemanticPathPart visitDotIdentifierSequence(HqlParser.DotIdentifierSequenceContext ctx) {
// final int numberOfContinuations = ctx.dotIdentifierSequenceContinuation().size();
// final boolean hasContinuations = numberOfContinuations != 0;
//
// final SemanticPathPart currentPathPart = semanticPathPartStack.getCurrent();
//
// SemanticPathPart result = currentPathPart.resolvePathPart(
// ctx.identifier().getText(),
// !hasContinuations,
// this
// );
//
// if ( hasContinuations ) {
// int i = 1;
// for ( HqlParser.DotIdentifierSequenceContinuationContext continuation : ctx.dotIdentifierSequenceContinuation() ) {
// result = result.resolvePathPart(
// continuation.identifier().getText(),
// i++ >= numberOfContinuations,
// this
// );
// }
// }
//
// return result;
// }
@Override
public SemanticPathPart visitDotIdentifierSequence(HqlParser.DotIdentifierSequenceContext ctx) {
final int numberOfContinuations = ctx.dotIdentifierSequenceContinuation().size();
final boolean hasContinuations = numberOfContinuations != 0;
final SemanticPathPart currentPathPart = semanticPathPartStack.getCurrent();
final DotIdentifierConsumer dotIdentifierConsumer = dotIdentifierConsumerStack.getCurrent();
SemanticPathPart result = currentPathPart.resolvePathPart(
dotIdentifierConsumer.consumeIdentifier(
ctx.identifier().getText(),
!hasContinuations,
this
true,
! hasContinuations
);
if ( hasContinuations ) {
int i = 1;
for ( HqlParser.DotIdentifierSequenceContinuationContext continuation : ctx.dotIdentifierSequenceContinuation() ) {
result = result.resolvePathPart(
dotIdentifierConsumer.consumeIdentifier(
continuation.identifier().getText(),
i++ >= numberOfContinuations,
this
false,
i++ >= numberOfContinuations
);
}
}
return result;
return dotIdentifierConsumer.getConsumedPart();
}
@Override
@ -2688,12 +2722,18 @@ public class SemanticQueryBuilder extends HqlParserBaseVisitor implements SqmCre
SqmPath<?> result = resolveTreatedPath( sqmPath, treatTarget );
if ( ctx.pathContinuation() != null ) {
semanticPathPartStack.push( result );
dotIdentifierConsumerStack.push(
new BasicDotIdentifierConsumer( result, this ) {
@Override
protected void reset() {
}
}
);
try {
result = consumeDomainPath( ctx.pathContinuation().dotIdentifierSequence() );
}
finally {
semanticPathPartStack.pop();
dotIdentifierConsumerStack.pop();
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.hql.spi;
/**
* Consumes the parts of a path.
*
* @author Steve Ebersole
*/
public interface DotIdentifierConsumer {
/**
* Responsible for consuming each part of the path. Called sequentially for
* each part.
*
* @param identifier The current part of the path being processed
* @param isBase Is this the base of the path (the first token)?
* @param isTerminal Is this the terminus of the path (last token)?
*/
void consumeIdentifier(String identifier, boolean isBase, boolean isTerminal);
/**
* Get the currently consumed part. Generally called after the whole path
* has been processed at which point this will return the final outcome of the
* consumption
*/
SemanticPathPart getConsumedPart();
}

View File

@ -18,7 +18,6 @@ import org.hibernate.query.sqm.SqmJoinable;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.consume.spi.SemanticQueryWalker;
import org.hibernate.query.sqm.produce.SqmCreationHelper;
import org.hibernate.query.sqm.produce.spi.SqmCreationState;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
@ -37,7 +36,6 @@ public abstract class AbstractSqmAttributeJoin<O,T>
implements SqmAttributeJoin<O,T> {
private static final Logger log = Logger.getLogger( AbstractSqmAttributeJoin.class );
private final SqmFrom<?,O> lhs;
private final boolean fetched;
private SqmPredicate onClausePredicate;
@ -59,13 +57,13 @@ public abstract class AbstractSqmAttributeJoin<O,T>
joinType,
nodeBuilder
);
this.lhs = lhs;
this.fetched = fetched;
}
@Override
public SqmFrom<?,O> getLhs() {
return lhs;
//noinspection unchecked
return (SqmFrom) super.getLhs();
}
@Override

View File

@ -10,6 +10,7 @@ import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
@ -35,8 +36,22 @@ public abstract class AbstractSqmPath<T> extends AbstractSqmExpression<T> implem
private final NavigablePath navigablePath;
private final SqmPath lhs;
/**
* Note that this field is only really used to support Criteria building.
* For HQL processing the {@link org.hibernate.query.hql.spi.SqmPathRegistry}
* serves the same purpose.
*/
private Map<String, SqmPath> attributePathRegistry;
/**
* For HQL processing - used to track implicit-join paths relative to this
* path. E.g., given `p.mate.mate` the SqmRoot identified by `p` would
* have an implicit-join for the `p.mate` path. Note however that the SqmPath
* for `p.mate` would not have one for `p.mate.mate` *unless* `p.mate.mate` were
* de-referenced somewhere else in the query.
*/
private Map<String,SqmPath<?>> implicitJoinPaths;
@SuppressWarnings("WeakerAccess")
protected AbstractSqmPath(
NavigablePath navigablePath,
@ -80,6 +95,32 @@ public abstract class AbstractSqmPath<T> extends AbstractSqmExpression<T> implem
return lhs;
}
@Override
public void visitImplicitJoinPaths(Consumer<SqmPath<?>> consumer) {
if ( implicitJoinPaths != null ) {
implicitJoinPaths.values().forEach( consumer );
}
}
@Override
public void registerImplicitJoinPath(SqmPath<?> path) {
assert path.getLhs() == this;
if ( implicitJoinPaths == null ) {
implicitJoinPaths = new HashMap<>();
}
final String relativeName = path.getNavigablePath().getLocalName();
final SqmPath<?> previous = implicitJoinPaths.put( relativeName, path );
if ( previous != null && previous != path ) {
log.debugf(
"Implicit-join path registration unexpectedly overrode previous registration - `%s`",
relativeName
);
}
}
@Override
public String getExplicitAlias() {
return getAlias();

View File

@ -6,6 +6,10 @@
*/
package org.hibernate.query.sqm.tree.domain;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.PathException;
@ -59,6 +63,16 @@ public interface SqmPath<T> extends SqmExpression<T>, SemanticPathPart, JpaPath<
*/
SqmPath<?> getLhs();
/**
* Visit each implicit-join path relative to this path
*/
void visitImplicitJoinPaths(Consumer<SqmPath<?>> consumer);
/**
* Register an implicit-join path relative to this path
*/
void registerImplicitJoinPath(SqmPath<?> path);
/**
* This node's type is its "referenced path source"
*/

View File

@ -0,0 +1,156 @@
/*
* 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.query.sqm;
import java.util.List;
import org.hibernate.query.sqm.AliasCollisionException;
import org.hibernate.query.sqm.produce.spi.ImplicitAliasGenerator;
import org.hibernate.query.sqm.tree.domain.SqmNavigableReference;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate;
import org.hibernate.query.sqm.tree.predicate.SqmInSubQueryPredicate;
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.testing.orm.domain.gambit.EntityWithManyToOneSelfReference;
import org.hibernate.testing.orm.domain.gambit.SimpleEntity;
import org.hibernate.testing.orm.junit.ExpectedException;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize;
import static org.hibernate.testing.hamcrest.CollectionMatchers.isEmpty;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test of all alias collision scenarios
*
* @author Steve Ebersole
* @author Andrea Boriero
*/
@SuppressWarnings("WeakerAccess")
public class AliasCollisionTest extends BaseSqmUnitTest {
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] {
EntityWithManyToOneSelfReference.class,
SimpleEntity.class,
EntityWithManyToOneSelfReference.class,
};
}
@Test
@ExpectedException( AliasCollisionException.class )
public void testDuplicateResultVariableCollision() {
// in both cases the query is using an alias (`b`) as 2 different
// select-clause result variables - that's an error
interpretSelect( "select a.someString as b, a.someInteger as b from SimpleEntity a" );
interpretSelect( "select a.someInteger as b, a.someInteger as b from SimpleEntity a" );
}
@Test
@ExpectedException( AliasCollisionException.class )
public void testResultVariableRenamesIdentificationVariableCollision() {
interpretSelect( "select a.someString as a from SimpleEntity as a" );
// NOTE that there is a special form of this rule. consider:
//
// select {alias} as {alias} from XYZ as {alias}
//
// this is valid because its just the explicit form of what happens
// when you have:
//
// select {alias} from XYZ as {alias}
}
@Test
@ExpectedException( AliasCollisionException.class )
public void testDuplicateIdentificationVariableCollision() {
interpretSelect( "select a from SimpleEntity as a, SimpleEntity as a" );
interpretSelect(
"select a.someString as b, a.someInteger as c from SimpleEntity a where a.someLong in " +
"(select b.someLong as e from SimpleEntity as b, SimpleEntity as b)"
);
interpretSelect(
"select a from EntityWithManyToOneSelfReference a left outer join a.other a on a.someInteger > 5"
);
}
@Test
public void testSameIdentificationVariablesInSubquery() {
final String query = "select a from SimpleEntity a where a.someString in (select a.someString from SimpleEntity a where a.someInteger = 5)";
final SqmSelectStatement sqm = interpretSelect( query );
final SqmQuerySpec querySpec = sqm.getQuerySpec();
final List<SqmSelection> selections = querySpec.getSelectClause().getSelections();
assertThat( selections, hasSize( 1 ) );
assertTrue( ImplicitAliasGenerator.isImplicitAlias( selections.get( 0 ).getAlias() ) );
final List<SqmRoot> roots = querySpec.getFromClause().getRoots();
assertThat( roots, hasSize( 1 ) );
assertThat( roots.get( 0 ).getJoins(), isEmpty() );
assertThat( roots.get( 0 ).getExplicitAlias(), is( "a" ) );
assertThat( querySpec.getWhereClause().getPredicate(), instanceOf( SqmInSubQueryPredicate.class ) );
final SqmInSubQueryPredicate predicate = (SqmInSubQueryPredicate) querySpec.getWhereClause().getPredicate();
final SqmRoot subQueryRoot = predicate.getSubQueryExpression()
.getQuerySpec()
.getFromClause()
.getRoots()
.get( 0 );
assertThat( subQueryRoot.getExplicitAlias(), is( "a" ) );
}
@Test
public void testSubqueryUsingIdentificationVariableDefinedInRootQuery() {
final String query = "select a from SimpleEntity a where a.someString in " +
"( select b.someString from SimpleEntity b where a.someLong = b.someLong )";
final SqmSelectStatement sqm = interpretSelect( query );
final SqmQuerySpec querySpec = sqm.getQuerySpec();
final List<SqmSelection> selections = querySpec.getSelectClause().getSelections();
assertThat( selections, hasSize( 1 ) );
assertTrue( ImplicitAliasGenerator.isImplicitAlias( selections.get( 0 ).getAlias() ) );
final List<SqmRoot> roots = querySpec.getFromClause().getRoots();
assertThat( roots, hasSize( 1 ) );
assertThat( roots.get( 0 ).getJoins(), isEmpty() );
assertThat( roots.get( 0 ).getExplicitAlias(), is( "a" ) );
assertThat( querySpec.getWhereClause().getPredicate(), instanceOf( SqmInSubQueryPredicate.class ) );
final SqmInSubQueryPredicate predicate = (SqmInSubQueryPredicate) querySpec.getWhereClause().getPredicate();
final SqmQuerySpec subQuerySpec = predicate.getSubQueryExpression().getQuerySpec();
assertThat(
subQuerySpec.getFromClause().getRoots().get( 0 ).getExplicitAlias(),
is( "b" )
);
final SqmComparisonPredicate correlation = (SqmComparisonPredicate) subQuerySpec.getWhereClause().getPredicate();
final SqmNavigableReference leftHandExpression = (SqmNavigableReference) correlation.getLeftHandExpression();
assertThat(
leftHandExpression.getLhs().getExplicitAlias(),
is( "a" )
);
final SqmNavigableReference rightHandExpression = (SqmNavigableReference) correlation.getRightHandExpression();
assertThat(
rightHandExpression.getLhs().getExplicitAlias(),
is( "b" )
);
}
}

View File

@ -0,0 +1,71 @@
/*
* 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.query.sqm;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.loader.spi.AfterLoadAction;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.exec.spi.Callback;
import org.hibernate.testing.junit5.StandardTags;
import org.hibernate.testing.orm.junit.BaseSessionFactoryFunctionalTest;
import org.junit.jupiter.api.Tag;
/**
* @author Steve Ebersole
*/
@Tag(StandardTags.SQM)
public abstract class BaseSqmUnitTest
extends BaseSessionFactoryFunctionalTest
implements SqlAstCreationContext, Callback {
@Override
protected void applySettings(StandardServiceRegistryBuilder builder) {
super.applySettings( builder );
builder.applySetting( AvailableSettings.JPA_QUERY_COMPLIANCE, strictJpaCompliance() );
}
@Override
protected boolean exportSchema() {
return false;
}
/**
* todo (6.0) : use JUnit parameters for this (see envers)
*/
protected boolean strictJpaCompliance() {
return false;
}
@Override
public void registerAfterLoadAction(AfterLoadAction afterLoadAction) {
}
protected SqmSelectStatement interpretSelect(String hql) {
return (SqmSelectStatement) sessionFactory().getQueryEngine().getSemanticQueryProducer().interpret( hql );
}
@Override
public MetamodelImplementor getDomainModel() {
return sessionFactory().getMetamodel();
}
@Override
public ServiceRegistry getServiceRegistry() {
return sessionFactory().getServiceRegistry();
}
@Override
public Integer getMaximumFetchDepth() {
return sessionFactory().getMaximumFetchDepth();
}
}

View File

@ -0,0 +1,37 @@
/*
* 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.query.sqm;
import org.hibernate.boot.MetadataSources;
import org.hibernate.jpa.spi.JpaCompliance;
import org.hibernate.testing.junit5.SessionFactoryBasedFunctionalTest;
import org.hibernate.testing.orm.domain.helpdesk.HelpDeskDomainModel;
import org.hibernate.testing.orm.domain.retail.RetailDomainModel;
/**
* Tests for type inference outside of what JPA says should be supported.
*
* NOTE : Distinguishing this from {@link JpaStandardSqmInferenceTests} allows
* applying {@link JpaCompliance#isJpaQueryComplianceEnabled()} testing for just
* these extensions
*
* @see JpaStandardSqmInferenceTests
*
* @author Steve Ebersole
*/
public class ExtensionSqmInferenceTests extends SessionFactoryBasedFunctionalTest {
@Override
protected void applyMetadataSources(MetadataSources metadataSources) {
super.applyMetadataSources( metadataSources );
HelpDeskDomainModel.INSTANCE.applyDomainModel( metadataSources );
RetailDomainModel.INSTANCE.applyDomainModel( metadataSources );
}
// todo (6.0) : add the checks ;)
}

View File

@ -0,0 +1,71 @@
/*
* 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.query.sqm;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Steve Ebersole
*/
public class FirstSqmUnitTest extends BaseSqmUnitTest {
// todo (6.0) : this test can likely just go away ultimately.
// it was intended just as a simple first "smoke" test
@Entity( name = "Person" )
public static class Person {
@Id
public Integer id;
public String name;
}
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] {
Person.class
};
}
@Test
public void testSelectRoot() {
final SqmSelectStatement sqm = interpretSelect( "select p from Person p" );
assertThat( sqm, notNullValue() );
assertThat(
sqm.getQuerySpec().getFromClause().getRoots().size(),
is( 1 )
);
assertThat(
sqm.getQuerySpec().getFromClause().getRoots().get( 0 ).getEntityName(),
is( sessionFactory().getMetamodel().findEntityDescriptor( Person.class ).getEntityName() )
);
}
@Test
public void testSelectId() {
final SqmSelectStatement sqm = interpretSelect( "select p.id from Person p" );
assertThat( sqm, notNullValue() );
assertThat(
sqm.getQuerySpec().getFromClause().getRoots().size(),
is( 1 )
);
assertThat(
sqm.getQuerySpec().getFromClause().getRoots().get( 0 ).getEntityName(),
is( sessionFactory().getMetamodel().findEntityDescriptor( Person.class ).getEntityName() )
);
}
}

View File

@ -0,0 +1,262 @@
/*
* 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.query.sqm;
import java.util.List;
import org.hibernate.orm.test.query.sqm.domain.Person;
import org.hibernate.query.SemanticException;
import org.hibernate.query.sqm.tree.SqmJoinType;
import org.hibernate.query.sqm.tree.from.SqmFromClause;
import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
import org.hibernate.query.sqm.tree.select.SqmSelection;
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hibernate.testing.hamcrest.CollectionMatchers.hasSize;
import static org.hibernate.testing.hamcrest.CollectionMatchers.isEmpty;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
/**
* Initial work on a "from clause processor"
*
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
public class FromClauseTests extends BaseSqmUnitTest {
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Person.class };
}
@Test
public void testSimpleFrom() {
final SqmSelectStatement selectStatement = interpretSelect( "select p.nickName from Person p" );
final SqmFromClause fromClause = selectStatement.getQuerySpec().getFromClause();
assertThat( fromClause, notNullValue() );
assertThat( fromClause.getRoots(), hasSize( 1 ) );
final SqmRoot firstRoot = fromClause.getRoots().get( 0 );
assertThat( firstRoot, notNullValue() );
assertThat( firstRoot.getJoins(), isEmpty() );
assertThat( firstRoot.getExplicitAlias(), is( "p") );
}
@Test
public void testMultipleSpaces() {
final SqmSelectStatement selectStatement = interpretSelect(
"select p.nickName from Person p, Person p2"
);
final SqmFromClause fromClause = selectStatement.getQuerySpec().getFromClause();
assertNotNull( fromClause );
assertThat( fromClause, notNullValue() );
assertThat( fromClause.getRoots(), hasSize( 2 ) );
final SqmRoot firstRoot = fromClause.getRoots().get( 0 );
assertThat( firstRoot, notNullValue() );
assertThat( firstRoot.getJoins(), isEmpty() );
assertThat( firstRoot.getExplicitAlias(), is( "p") );
final SqmRoot secondRoot = fromClause.getRoots().get( 0 );
assertThat( secondRoot, notNullValue() );
assertThat( secondRoot.getJoins(), isEmpty() );
assertThat( secondRoot.getExplicitAlias(), is( "p") );
}
@Test
public void testImplicitAlias() {
final SqmSelectStatement selectStatement = interpretSelect( "select nickName from Person" );
final SqmFromClause fromClause = selectStatement.getQuerySpec().getFromClause();
assertThat( fromClause, notNullValue() );
assertThat( fromClause.getRoots(), hasSize( 1 ) );
final SqmRoot sqmRoot = fromClause.getRoots().get( 0 );
assertThat( sqmRoot, notNullValue() );
assertThat( sqmRoot.getJoins(), isEmpty() );
assertThat( sqmRoot.getExplicitAlias(), nullValue() );
}
@Test
public void testCrossJoin() {
final SqmSelectStatement selectStatement = interpretSelect(
"select p.nickName from Person p cross join Person p2"
);
final SqmFromClause fromClause = selectStatement.getQuerySpec().getFromClause();
assertThat( fromClause, notNullValue() );
assertThat( fromClause.getRoots(), hasSize( 1 ) );
final SqmRoot<?> sqmRoot = fromClause.getRoots().get( 0 );
assertThat( sqmRoot, notNullValue() );
assertThat( sqmRoot.getExplicitAlias(), is( "p" ) );
assertThat( sqmRoot.getJoins(), hasSize( 1 ) );
assertThat( sqmRoot.getSqmJoins().get( 0 ).getExplicitAlias(), is( "p2" ) );
}
@Test
public void testSimpleImplicitInnerJoin() {
simpleJoinAssertions(
interpretSelect( "select p.nickName from Person p join p.mate m" ),
SqmJoinType.INNER,
"p",
"m"
);
}
private void simpleJoinAssertions(
SqmSelectStatement selectStatement,
SqmJoinType joinType,
String rootAlias,
String joinAlias) {
final SqmFromClause fromClause = selectStatement.getQuerySpec().getFromClause();
assertThat( fromClause, notNullValue() );
assertThat( fromClause.getRoots(), hasSize( 1 ) );
final SqmRoot<?> sqmRoot = fromClause.getRoots().get( 0 );
assertThat( sqmRoot, notNullValue() );
assertThat( sqmRoot.getExplicitAlias(), is( rootAlias ) );
assertThat( sqmRoot.getJoins(), hasSize( 1 ) );
assertThat( sqmRoot.getSqmJoins().get( 0 ).getExplicitAlias(), is( joinAlias ) );
assertThat( sqmRoot.getSqmJoins().get( 0 ).getSqmJoinType(), is( joinType ) );
}
@Test
public void testSimpleExplicitInnerJoin() {
simpleJoinAssertions(
interpretSelect( "select a.nickName from Person a inner join a.mate c" ),
SqmJoinType.INNER,
"a",
"c"
);
}
@Test
public void testSimpleExplicitOuterJoin() {
simpleJoinAssertions(
interpretSelect( "select a.nickName from Person a outer join a.mate c" ),
SqmJoinType.LEFT,
"a",
"c"
);
}
@Test
public void testSimpleExplicitLeftOuterJoin() {
simpleJoinAssertions(
interpretSelect( "select a.nickName from Person a left outer join a.mate c" ),
SqmJoinType.LEFT,
"a",
"c"
);
}
@Test
public void testAttributeJoinWithOnClause() {
final SqmSelectStatement selectStatement = interpretSelect(
"select a from Person a left outer join a.mate c on c.numberOfToes > 5 and c.numberOfToes < 20 "
);
simpleJoinAssertions(
selectStatement,
SqmJoinType.LEFT,
"a",
"c"
);
// todo (6.0) : check join restrictions
// not yet tracked, nor exposed. SqmPredicate
// final SqmJoin join = selectStatement.getQuerySpec()
// .getFromClause()
// .getFromElementSpaces()
// .get( 0 )
// .getJoins()
// .get( 0 );
}
@Test
public void testPathExpression() {
final String query = "select p.mate from Person p";
SqmSelectStatement selectStatement = interpretSelect( query );
final SqmFromClause fromClause = selectStatement.getQuerySpec().getFromClause();
assertThat( fromClause, notNullValue() );
assertThat( fromClause.getRoots(), hasSize( 1 ) );
final SqmRoot<?> sqmRoot = fromClause.getRoots().get( 0 );
assertThat( sqmRoot, notNullValue() );
assertThat( sqmRoot.getExplicitAlias(), is( "p" ) );
assertThat( sqmRoot.getSqmJoins(), hasSize( 0 ) );
}
@Test
public void testFromElementReferenceInSelect() {
final String query = "select p from Person p";
SqmSelectStatement selectStatement = interpretSelect( query );
final SqmFromClause fromClause = selectStatement.getQuerySpec().getFromClause();
assertThat( fromClause, notNullValue() );
assertThat( fromClause.getRoots(), hasSize( 1 ) );
final SqmRoot<?> sqmRoot = fromClause.getRoots().get( 0 );
assertThat( sqmRoot, notNullValue() );
assertThat( selectStatement.getQuerySpec().getSelectClause().getSelections(), hasSize( 1 ) );
final SqmSelection sqmSelection = selectStatement.getQuerySpec().getSelectClause().getSelections().get( 0 );
assertThat( sqmSelection.getSelectableNode(), instanceOf( SqmRoot.class ) );
}
@Test
public void testFromElementReferenceInOrderBy() {
final String query = "select p from Person p order by p";
SqmSelectStatement selectStatement = interpretSelect( query );
final SqmFromClause fromClause = selectStatement.getQuerySpec().getFromClause();
assertThat( fromClause, notNullValue() );
assertThat( fromClause.getRoots(), hasSize( 1 ) );
final List<SqmSortSpecification> orderBy = selectStatement.getQuerySpec()
.getOrderByClause()
.getSortSpecifications();
assertThat( orderBy, hasSize( 1 ) );
assertThat( orderBy.get( 0 ).getSortExpression(), instanceOf( SqmRoot.class ) );
}
@Test
public void testCrossSpaceReferencesFail() {
final String query = "select p from Person p, Person p2 join Person p3 on p3.id = p.id";
try {
interpretSelect( query );
fail( "Expecting failure" );
}
catch (SemanticException e) {
assertThat( e.getMessage(), startsWith( "SqmQualifiedJoin predicate referred to SqmRoot [" ) );
assertThat( e.getMessage(), containsString( "] other than the join's root [" ) );
}
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.query.sqm;
import javax.money.MonetaryAmount;
import org.hibernate.boot.MetadataSources;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.testing.junit5.SessionFactoryBasedFunctionalTest;
import org.hibernate.testing.orm.domain.helpdesk.HelpDeskDomainModel;
import org.hibernate.testing.orm.domain.helpdesk.Status;
import org.hibernate.testing.orm.domain.retail.RetailDomainModel;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
/**
* Tests for "type inference specifically limited to the cases JPA
* says should be supported.
*
* @see ExtensionSqmInferenceTests
*
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
public class JpaStandardSqmInferenceTests extends SessionFactoryBasedFunctionalTest {
protected void applyMetadataSources(MetadataSources metadataSources) {
super.applyMetadataSources( metadataSources );
HelpDeskDomainModel.INSTANCE.applyDomainModel( metadataSources );
RetailDomainModel.INSTANCE.applyDomainModel( metadataSources );
}
@Test
public void testEnumInference() {
checkParameters(
"from Account a where a.loginStatus = :status",
Status.class
);
checkParameters(
"from Account a where a.loginStatus <> :status",
Status.class
);
checkParameters(
"from Account a where a.loginStatus != :status",
Status.class
);
checkParameters(
"from Account a where a.loginStatus > :status",
Status.class
);
checkParameters(
"from Account a where a.loginStatus >= :status",
Status.class
);
checkParameters(
"from Account a where a.loginStatus < :status",
Status.class
);
checkParameters(
"from Account a where a.loginStatus <= :status",
Status.class
);
}
@Test
public void testConvertedInference() {
checkParameters(
"select l from LineItem l where l.subTotal = :limit",
MonetaryAmount.class
);
checkParameters(
"select l from LineItem l where l.subTotal <> :limit",
MonetaryAmount.class
);
checkParameters(
"select l from LineItem l where l.subTotal != :limit",
MonetaryAmount.class
);
checkParameters(
"select l from LineItem l where l.subTotal > :limit",
MonetaryAmount.class
);
checkParameters(
"select l from LineItem l where l.subTotal >= :limit",
MonetaryAmount.class
);
checkParameters(
"select l from LineItem l where l.subTotal < :limit",
MonetaryAmount.class
);
checkParameters(
"select l from LineItem l where l.subTotal <= :limit",
MonetaryAmount.class
);
}
private void checkParameters(String query, Class<?>... expecteds) {
final SqmStatement sqmStatement = sessionFactory().getQueryEngine().getSemanticQueryProducer().interpret( query );
checkParameterTypes( sqmStatement, expecteds );
}
private void checkParameterTypes(SqmStatement<?> sqmStatement, Class<?>[] expectedParameterTypes) {
assertEquals( expectedParameterTypes.length, sqmStatement.getSqmParameters().size() );
int count = 0;
for ( SqmParameter<?> queryParameter : sqmStatement.getSqmParameters() ) {
assertEquals(
"Anticipated type for query parameter [" + queryParameter + "]",
expectedParameterTypes[count++],
queryParameter.getAnticipatedType().getExpressableJavaTypeDescriptor().getJavaType()
);
// assertThat(
// queryParameter.getAnticipatedType().getJavaType(),
// AssignableMatcher.assignableTo( expectedParameterTypes[count++] )
// );
}
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.query.sqm.domain;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("WeakerAccess")
public class ConstructedLookupListItem implements LookupListItem {
private final Integer id;
private final String displayValue;
public ConstructedLookupListItem(Integer id, String displayValue) {
this.id = id;
this.displayValue = displayValue;
}
@Override
public Integer getId() {
return id;
}
@Override
public String getDisplayValue() {
return displayValue;
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.query.sqm.domain;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author Steve Ebersole
*/
@Entity
public class CrazyHqlKeywordEntity {
@Id
public Integer id;
public String name;
public String from;
public String select;
public String order;
}

View File

@ -0,0 +1,34 @@
/*
* 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.query.sqm.domain;
/**
* @author Steve Ebersole
*/
@SuppressWarnings("unused")
public class InjectedLookupListItem implements LookupListItem {
private Integer id;
private String displayValue;
@Override
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String getDisplayValue() {
return displayValue;
}
public void setDisplayValue(String displayValue) {
this.displayValue = displayValue;
}
}

View File

@ -0,0 +1,16 @@
/*
* 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.query.sqm.domain;
/**
* @author Steve Ebersole
*/
public interface LookupListItem {
Integer getId();
String getDisplayValue();
}

View File

@ -0,0 +1,27 @@
/*
* 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.query.sqm.domain;
/**
* @author Steve Ebersole
*/
public class NestedCtorLookupListItem extends ConstructedLookupListItem implements NestedLookupListItem {
private final LookupListItem nested;
public NestedCtorLookupListItem(
Integer id,
String displayValue,
LookupListItem nested) {
super( id, displayValue );
this.nested = nested;
}
@Override
public LookupListItem getNested() {
return nested;
}
}

View File

@ -0,0 +1,23 @@
/*
* 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.query.sqm.domain;
/**
* @author Steve Ebersole
*/
public class NestedInjectedLookupListItem extends InjectedLookupListItem implements NestedLookupListItem {
private LookupListItem nested;
public void setNested(LookupListItem nested) {
this.nested = nested;
}
@Override
public LookupListItem getNested() {
return nested;
}
}

View File

@ -0,0 +1,14 @@
/*
* 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.query.sqm.domain;
/**
* @author Steve Ebersole
*/
public interface NestedLookupListItem extends LookupListItem {
LookupListItem getNested();
}

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.orm.test.query.sqm.domain;
import java.time.Instant;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
/**
* @author Steve Ebersole
*/
@Entity
public class Person {
private Integer pk;
private Name name;
private String nickName;
private Instant dob;
private int numberOfToes;
private Person mate;
@Id
public Integer getPk() {
return pk;
}
public void setPk(Integer pk) {
this.pk = pk;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
@Temporal( TemporalType.TIMESTAMP )
public Instant getDob() {
return dob;
}
public void setDob(Instant dob) {
this.dob = dob;
}
public int getNumberOfToes() {
return numberOfToes;
}
public void setNumberOfToes(int numberOfToes) {
this.numberOfToes = numberOfToes;
}
@ManyToOne
@JoinColumn
public Person getMate() {
return mate;
}
public void setMate(Person mate) {
this.mate = mate;
}
@Embeddable
public static class Name {
public String first;
public String last;
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
}
}

View File

@ -1,165 +0,0 @@
<?xml version="1.0"?>
<!--
~ 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>.
-->
<!DOCTYPE hibernate-mapping SYSTEM "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd" >
<hibernate-mapping
package="org.hibernate.test.hql"
default-access="field">
<class name="Animal">
<id name="id">
<generator class="native"/>
</id>
<property name="description"/>
<property name="bodyWeight" column="body_weight"/>
<many-to-one name="mother" column="mother_id"/>
<many-to-one name="father" column="father_id"/>
<many-to-one name="zoo" column="zoo_id"/>
<property name="serialNumber"/>
<set name="offspring" order-by="father_id">
<key column="mother_id"/>
<one-to-many class="Animal"/>
</set>
<joined-subclass name="Reptile">
<key column="animal"/>
<property name="bodyTemperature"/>
<joined-subclass name="Lizard">
<key column="reptile"/>
</joined-subclass>
</joined-subclass>
<joined-subclass name="Mammal">
<key column="animal"/>
<property name="pregnant"/>
<property name="birthdate" type="date"/>
<joined-subclass name="DomesticAnimal">
<key column="mammal"/>
<many-to-one name="owner"/>
<joined-subclass name="Cat">
<key column="mammal"/>
</joined-subclass>
<joined-subclass name="Dog">
<key column="mammal"/>
</joined-subclass>
</joined-subclass>
<joined-subclass name="Human">
<key column="mammal"/>
<component name="name">
<property name="first" column="name_first"/>
<property name="initial" column="name_initial"/>
<property name="last" column="name_last"/>
</component>
<property name="nickName"/>
<property name="heightInches">
<column name="height_centimeters"
not-null="true"
read="height_centimeters / 2.54E0"
write="? * 2.54E0"/>
</property>
<property name="intValue"/>
<property name="floatValue"/>
<property name="bigDecimalValue"/>
<property name="bigIntegerValue"/>
<bag name="friends">
<key column="human1"/>
<many-to-many column="human2" class="Human"/>
</bag>
<map name="family">
<key column="human1"/>
<map-key column="relationship" type="string"/>
<many-to-many column="human2" class="Human"/>
</map>
<bag name="pets" inverse="true">
<key column="owner"/>
<one-to-many class="DomesticAnimal"/>
</bag>
<set name="nickNames" lazy="false" table="human_nick_names" sort="natural">
<key column="human"/>
<element column="nick_name" type="string" not-null="true"/>
</set>
<map name="addresses" table="addresses">
<key column="human"/>
<map-key type="string" column="`type`"/>
<composite-element class="Address">
<property name="street"/>
<property name="city"/>
<property name="postalCode"/>
<property name="country"/>
<many-to-one name="stateProvince" column="state_prov_id" class="StateProvince"/>
</composite-element>
</map>
</joined-subclass>
</joined-subclass>
</class>
<class name="User" table="`User`">
<id name="id">
<generator class="foreign">
<param name="property">human</param>
</generator>
</id>
<property name="userName"/>
<one-to-one name="human" constrained="true"/>
<list name="permissions">
<key column="userId"/>
<list-index column="permissionId"/>
<element type="string" column="permissionName"/>
</list>
</class>
<class name="Zoo" discriminator-value="Z">
<id name="id">
<generator class="native"/>
</id>
<discriminator column="zooType" type="character"/>
<property name="name" type="string"/>
<property name="classification" type="org.hibernate.test.hql.ClassificationType"/>
<map name="directors">
<key column="directorZoo_id"/>
<index type="string" column="`title`"/>
<many-to-many class="Human"/>
</map>
<map name="mammals">
<key column="mammalZoo_id"/>
<index type="string" column="name"/>
<one-to-many class="Mammal"/>
</map>
<map name="animals" inverse="true">
<key column="zoo_id"/>
<index type="string" column="serialNumber"/>
<one-to-many class="Animal"/>
</map>
<component name="address" class="Address">
<property name="street"/>
<property name="city"/>
<property name="postalCode"/>
<property name="country"/>
<many-to-one name="stateProvince" column="state_prov_id" class="StateProvince"/>
</component>
<subclass name="PettingZoo" discriminator-value="P"/>
</class>
<class name="StateProvince">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="isoCode"/>
</class>
<class name="Joiner">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<join table="JOINED">
<key column="ID"/>
<property name="joinedName"/>
</join>
</class>
</hibernate-mapping>

View File

@ -0,0 +1,35 @@
/*
* 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.testing.hamcrest;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
/**
* @author Steve Ebersole
*/
public class AssignableMatcher extends BaseMatcher<Class<?>> {
public static final AssignableMatcher assignableTo(Class<?> expected) {
return new AssignableMatcher( expected );
}
private final Class<?> expected;
public AssignableMatcher(Class<?> expected) {
this.expected = expected;
}
@Override
public boolean matches(Object item) {
return expected.isAssignableFrom( item.getClass() );
}
@Override
public void describeTo(Description description) {
description.appendText( "assignable to " ).appendText( expected.getName() );
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.testing.hamcrest;
import java.util.Collection;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
/**
* @author Steve Ebersole
*/
public class CollectionEmptinessMatcher extends BaseMatcher<Collection> {
private final boolean emptyIsMatch;
public CollectionEmptinessMatcher(boolean emptyIsMatch) {
this.emptyIsMatch = emptyIsMatch;
}
@Override
public boolean matches(Object item) {
if ( emptyIsMatch ) {
return ( (Collection) item ).isEmpty();
}
else {
return ! ( (Collection) item ).isEmpty();
}
}
@Override
public void describeTo(Description description) {
if ( emptyIsMatch ) {
description.appendText( "<is empty>" );
}
else {
description.appendText( "<is not empty>" );
}
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.testing.hamcrest;
import java.util.Collection;
import org.hamcrest.Matcher;
/**
* @author Steve Ebersole
*/
public class CollectionMatchers {
private static final CollectionEmptinessMatcher IS_EMPTY = new CollectionEmptinessMatcher( true );
private static final CollectionEmptinessMatcher IS_NOT_EMPTY = new CollectionEmptinessMatcher( false );
public static Matcher<Collection> isEmpty() {
return IS_EMPTY;
}
public static Matcher<Collection> isNotEmpty() {
return IS_NOT_EMPTY;
}
public static Matcher<Collection<?>> hasSize(int size) {
return org.hamcrest.Matchers.hasSize( size );
}
}

View File

@ -0,0 +1,33 @@
/*
* 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.testing.hamcrest;
import java.util.Collection;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
/**
* @author Steve Ebersole
*/
public class CollectionSizeMatcher extends BaseMatcher<Collection> {
private final int size;
public CollectionSizeMatcher(int size) {
this.size = size;
}
@Override
public boolean matches(Object item) {
return ( (Collection) item ).size() == size;
}
@Override
public void describeTo(Description description) {
description.appendValue( size );
}
}