6 - SQM based on JPA type system
- moving SQM-specific tests from wip/6.0
This commit is contained in:
parent
00da979e70
commit
b101ffbf79
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() );
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
*/
|
||||
|
|
|
@ -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" )
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 ;)
|
||||
}
|
|
@ -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() )
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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 [" ) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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++] )
|
||||
// );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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() );
|
||||
}
|
||||
}
|
|
@ -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>" );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue