HHH-13619 - Support for JPA's `size` function as a select expression
- initial support
This commit is contained in:
parent
24cedfa6ec
commit
692f19c83f
|
@ -244,6 +244,14 @@ tokens
|
||||||
protected void handleResultVariableRef(AST resultVariableRef) throws SemanticException {
|
protected void handleResultVariableRef(AST resultVariableRef) throws SemanticException {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected AST createCollectionSizeFunction(AST collectionPath, boolean inSelect) throws SemanticException {
|
||||||
|
throw new UnsupportedOperationException( "Walker should implement" );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AST createCollectionPath(AST qualifier, AST reference) throws SemanticException {
|
||||||
|
throw new UnsupportedOperationException( "Walker should implement" );
|
||||||
|
}
|
||||||
|
|
||||||
protected AST lookupProperty(AST dot,boolean root,boolean inSelect) throws SemanticException {
|
protected AST lookupProperty(AST dot,boolean root,boolean inSelect) throws SemanticException {
|
||||||
return dot;
|
return dot;
|
||||||
}
|
}
|
||||||
|
@ -683,7 +691,10 @@ collectionFunction
|
||||||
;
|
;
|
||||||
|
|
||||||
functionCall
|
functionCall
|
||||||
: #(METHOD_CALL {inFunctionCall=true;} pathAsIdent ( #(EXPR_LIST (exprOrSubquery [ null ])* ) )? ) {
|
: #( COLL_SIZE path:collectionPath ) {
|
||||||
|
#functionCall = createCollectionSizeFunction( #path, inSelect );
|
||||||
|
}
|
||||||
|
| #(METHOD_CALL {inFunctionCall=true;} pathAsIdent ( #(EXPR_LIST (exprOrSubquery [ null ])* ) )? ) {
|
||||||
processFunction( #functionCall, inSelect );
|
processFunction( #functionCall, inSelect );
|
||||||
inFunctionCall=false;
|
inFunctionCall=false;
|
||||||
}
|
}
|
||||||
|
@ -694,6 +705,18 @@ functionCall
|
||||||
| #(AGGREGATE aggregateExpr )
|
| #(AGGREGATE aggregateExpr )
|
||||||
;
|
;
|
||||||
|
|
||||||
|
collectionPath!
|
||||||
|
// for now we do not support nested path refs.
|
||||||
|
: #( COLL_PATH ref:identifier (qualifier:collectionPathQualifier)? ) {
|
||||||
|
resolve( #qualifier );
|
||||||
|
#collectionPath = createCollectionPath( #qualifier, #ref );
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
collectionPathQualifier
|
||||||
|
: addrExpr [true]
|
||||||
|
;
|
||||||
|
|
||||||
constant
|
constant
|
||||||
: literal
|
: literal
|
||||||
| NULL
|
| NULL
|
||||||
|
|
|
@ -109,6 +109,7 @@ tokens
|
||||||
CONSTRUCTOR;
|
CONSTRUCTOR;
|
||||||
CASE2; // a "simple case statement", whereas CASE represents a "searched case statement"
|
CASE2; // a "simple case statement", whereas CASE represents a "searched case statement"
|
||||||
CAST;
|
CAST;
|
||||||
|
COLL_PATH;
|
||||||
EXPR_LIST;
|
EXPR_LIST;
|
||||||
FILTER_ENTITY; // FROM element injected because of a filter expression (happens during compilation phase 2)
|
FILTER_ENTITY; // FROM element injected because of a filter expression (happens during compilation phase 2)
|
||||||
IN_LIST;
|
IN_LIST;
|
||||||
|
@ -124,6 +125,7 @@ tokens
|
||||||
RANGE;
|
RANGE;
|
||||||
ROW_STAR;
|
ROW_STAR;
|
||||||
SELECT_FROM;
|
SELECT_FROM;
|
||||||
|
COLL_SIZE;
|
||||||
UNARY_MINUS;
|
UNARY_MINUS;
|
||||||
UNARY_PLUS;
|
UNARY_PLUS;
|
||||||
VECTOR_EXPR; // ( x, y, z )
|
VECTOR_EXPR; // ( x, y, z )
|
||||||
|
@ -723,6 +725,7 @@ atom
|
||||||
primaryExpression
|
primaryExpression
|
||||||
: { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax
|
: { validateSoftKeyword("function") && LA(2) == OPEN && LA(3) == QUOTED_STRING }? jpaFunctionSyntax
|
||||||
| { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction
|
| { validateSoftKeyword("cast") && LA(2) == OPEN }? castFunction
|
||||||
|
| { validateSoftKeyword("size") && LA(2) == OPEN }? collectionSizeFunction
|
||||||
| identPrimary ( options {greedy=true;} : DOT^ "class" )?
|
| identPrimary ( options {greedy=true;} : DOT^ "class" )?
|
||||||
| constant
|
| constant
|
||||||
| parameter
|
| parameter
|
||||||
|
@ -762,6 +765,26 @@ castTargetType
|
||||||
: identifier { handleDotIdent(); } ( options { greedy=true; } : DOT^ identifier )*
|
: identifier { handleDotIdent(); } ( options { greedy=true; } : DOT^ identifier )*
|
||||||
;
|
;
|
||||||
|
|
||||||
|
collectionSizeFunction!
|
||||||
|
: s:IDENT OPEN p:collectionPath CLOSE {
|
||||||
|
assert #s.getText().equalsIgnoreCase( "size" );
|
||||||
|
#collectionSizeFunction = #( [COLL_SIZE], #p );
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
collectionPath!
|
||||||
|
// for now we do not support nested path refs (for embeddables)
|
||||||
|
: simpleRef:identifier {
|
||||||
|
#collectionPath = #( [COLL_PATH], #simpleRef );
|
||||||
|
}
|
||||||
|
| qualifier:collectionPathQualifier DOT propertyName:identifier {
|
||||||
|
#collectionPath = #( [COLL_PATH], #propertyName, #qualifier );
|
||||||
|
};
|
||||||
|
|
||||||
|
collectionPathQualifier
|
||||||
|
: identifier ( DOT^ { weakKeywords(); } identifier )*
|
||||||
|
;
|
||||||
|
|
||||||
parameter
|
parameter
|
||||||
: COLON^ { expectNamedParameterName(); } IDENT
|
: COLON^ { expectNamedParameterName(); } IDENT
|
||||||
| PARAM^ (NUM_INT)?
|
| PARAM^ (NUM_INT)?
|
||||||
|
|
|
@ -127,6 +127,10 @@ options {
|
||||||
protected String renderOrderByElement(String expression, String order, String nulls) {
|
protected String renderOrderByElement(String expression, String order, String nulls) {
|
||||||
throw new UnsupportedOperationException("Concrete SQL generator should override this method.");
|
throw new UnsupportedOperationException("Concrete SQL generator should override this method.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void renderCollectionSize(AST collectionSizeNode) {
|
||||||
|
throw new UnsupportedOperationException( "Concrete SQL generator should override this method." );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statement
|
statement
|
||||||
|
@ -478,6 +482,9 @@ methodCall
|
||||||
( #(EXPR_LIST (arguments)? ) )?
|
( #(EXPR_LIST (arguments)? ) )?
|
||||||
{ endFunctionTemplate(m); } )
|
{ endFunctionTemplate(m); } )
|
||||||
| #( c:CAST { beginFunctionTemplate(c,c); } castExpression {betweenFunctionArguments();} castTargetType { endFunctionTemplate(c); } )
|
| #( c:CAST { beginFunctionTemplate(c,c); } castExpression {betweenFunctionArguments();} castTargetType { endFunctionTemplate(c); } )
|
||||||
|
| cs:COLL_SIZE {
|
||||||
|
renderCollectionSize( #cs );
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
arguments
|
arguments
|
||||||
|
|
|
@ -109,6 +109,7 @@ public final class HqlParser extends HqlBaseParser {
|
||||||
return parseErrorHandler;
|
return parseErrorHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides the base behavior to retry keywords as identifiers.
|
* Overrides the base behavior to retry keywords as identifiers.
|
||||||
*
|
*
|
||||||
|
|
|
@ -34,6 +34,8 @@ import org.hibernate.hql.internal.ast.tree.AggregateNode;
|
||||||
import org.hibernate.hql.internal.ast.tree.AssignmentSpecification;
|
import org.hibernate.hql.internal.ast.tree.AssignmentSpecification;
|
||||||
import org.hibernate.hql.internal.ast.tree.CastFunctionNode;
|
import org.hibernate.hql.internal.ast.tree.CastFunctionNode;
|
||||||
import org.hibernate.hql.internal.ast.tree.CollectionFunction;
|
import org.hibernate.hql.internal.ast.tree.CollectionFunction;
|
||||||
|
import org.hibernate.hql.internal.ast.tree.CollectionPathNode;
|
||||||
|
import org.hibernate.hql.internal.ast.tree.CollectionSizeNode;
|
||||||
import org.hibernate.hql.internal.ast.tree.ConstructorNode;
|
import org.hibernate.hql.internal.ast.tree.ConstructorNode;
|
||||||
import org.hibernate.hql.internal.ast.tree.DeleteStatement;
|
import org.hibernate.hql.internal.ast.tree.DeleteStatement;
|
||||||
import org.hibernate.hql.internal.ast.tree.DotNode;
|
import org.hibernate.hql.internal.ast.tree.DotNode;
|
||||||
|
@ -82,6 +84,7 @@ import org.hibernate.param.VersionTypeSeedParameterSpecification;
|
||||||
import org.hibernate.persister.collection.CollectionPropertyNames;
|
import org.hibernate.persister.collection.CollectionPropertyNames;
|
||||||
import org.hibernate.persister.collection.QueryableCollection;
|
import org.hibernate.persister.collection.QueryableCollection;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
import org.hibernate.persister.entity.PropertyMapping;
|
||||||
import org.hibernate.persister.entity.Queryable;
|
import org.hibernate.persister.entity.Queryable;
|
||||||
import org.hibernate.sql.JoinType;
|
import org.hibernate.sql.JoinType;
|
||||||
import org.hibernate.type.AssociationType;
|
import org.hibernate.type.AssociationType;
|
||||||
|
@ -639,6 +642,13 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par
|
||||||
return impliedJoinType;
|
return impliedJoinType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AST createCollectionSizeFunction(AST collectionPath, boolean inSelect) throws SemanticException {
|
||||||
|
assert collectionPath instanceof CollectionPathNode;
|
||||||
|
|
||||||
|
return new CollectionSizeNode( (CollectionPathNode) collectionPath, this );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AST lookupProperty(AST dot, boolean root, boolean inSelect) throws SemanticException {
|
protected AST lookupProperty(AST dot, boolean root, boolean inSelect) throws SemanticException {
|
||||||
DotNode dotNode = (DotNode) dot;
|
DotNode dotNode = (DotNode) dot;
|
||||||
|
@ -1221,6 +1231,11 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par
|
||||||
indexNode.resolve( true, true );
|
indexNode.resolve( true, true );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AST createCollectionPath(AST qualifier, AST reference) throws SemanticException {
|
||||||
|
return CollectionPathNode.from( qualifier, reference, this );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void processFunction(AST functionCall, boolean inSelect) throws SemanticException {
|
protected void processFunction(AST functionCall, boolean inSelect) throws SemanticException {
|
||||||
MethodNode methodNode = (MethodNode) functionCall;
|
MethodNode methodNode = (MethodNode) functionCall;
|
||||||
|
|
|
@ -17,6 +17,8 @@ import org.hibernate.dialect.function.SQLFunction;
|
||||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.hql.internal.antlr.SqlGeneratorBase;
|
import org.hibernate.hql.internal.antlr.SqlGeneratorBase;
|
||||||
import org.hibernate.hql.internal.antlr.SqlTokenTypes;
|
import org.hibernate.hql.internal.antlr.SqlTokenTypes;
|
||||||
|
import org.hibernate.hql.internal.ast.tree.CollectionPathNode;
|
||||||
|
import org.hibernate.hql.internal.ast.tree.CollectionSizeNode;
|
||||||
import org.hibernate.hql.internal.ast.tree.FromElement;
|
import org.hibernate.hql.internal.ast.tree.FromElement;
|
||||||
import org.hibernate.hql.internal.ast.tree.FunctionNode;
|
import org.hibernate.hql.internal.ast.tree.FunctionNode;
|
||||||
import org.hibernate.hql.internal.ast.tree.Node;
|
import org.hibernate.hql.internal.ast.tree.Node;
|
||||||
|
@ -32,6 +34,7 @@ import org.hibernate.param.ParameterSpecification;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
import antlr.RecognitionException;
|
import antlr.RecognitionException;
|
||||||
|
import antlr.SemanticException;
|
||||||
import antlr.collections.AST;
|
import antlr.collections.AST;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -426,4 +429,19 @@ public class SqlGenerator extends SqlGeneratorBase implements ErrorReporter {
|
||||||
);
|
);
|
||||||
return sessionFactory.getDialect().renderOrderByElement( expression, null, order, nullPrecedence );
|
return sessionFactory.getDialect().renderOrderByElement( expression, null, order, nullPrecedence );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void renderCollectionSize(AST ast) {
|
||||||
|
assert ast instanceof CollectionSizeNode;
|
||||||
|
|
||||||
|
final CollectionSizeNode collectionSizeNode = (CollectionSizeNode) ast;
|
||||||
|
|
||||||
|
// todo : or `#getStringBuilder()` directly?
|
||||||
|
try {
|
||||||
|
writer.clause( collectionSizeNode.toSqlExpression() );
|
||||||
|
}
|
||||||
|
catch (SemanticException e) {
|
||||||
|
throw new QueryException( "Unable to render collection-size node" );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.hql.internal.ast.HqlSqlWalker;
|
import org.hibernate.hql.internal.ast.HqlSqlWalker;
|
||||||
import org.hibernate.hql.internal.ast.SqlGenerator;
|
import org.hibernate.hql.internal.ast.SqlGenerator;
|
||||||
import org.hibernate.hql.internal.ast.tree.DeleteStatement;
|
import org.hibernate.hql.internal.ast.tree.DeleteStatement;
|
||||||
|
import org.hibernate.hql.internal.ast.tree.FromElement;
|
||||||
import org.hibernate.metamodel.spi.MetamodelImplementor;
|
import org.hibernate.metamodel.spi.MetamodelImplementor;
|
||||||
import org.hibernate.param.ParameterSpecification;
|
import org.hibernate.param.ParameterSpecification;
|
||||||
import org.hibernate.persister.collection.AbstractCollectionPersister;
|
import org.hibernate.persister.collection.AbstractCollectionPersister;
|
||||||
|
|
|
@ -0,0 +1,248 @@
|
||||||
|
/*
|
||||||
|
* 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.hql.internal.ast.tree;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.hibernate.QueryException;
|
||||||
|
import org.hibernate.hql.internal.ast.HqlSqlWalker;
|
||||||
|
import org.hibernate.hql.internal.ast.SqlASTFactory;
|
||||||
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
|
import org.hibernate.persister.collection.QueryableCollection;
|
||||||
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
|
import org.hibernate.persister.entity.PropertyMapping;
|
||||||
|
import org.hibernate.type.CollectionType;
|
||||||
|
import org.hibernate.type.CompositeType;
|
||||||
|
import org.hibernate.type.EntityType;
|
||||||
|
import org.hibernate.type.Type;
|
||||||
|
|
||||||
|
import antlr.SemanticException;
|
||||||
|
import antlr.collections.AST;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class CollectionPathNode extends SqlNode {
|
||||||
|
/**
|
||||||
|
* Used to resolve the collection "owner key" columns
|
||||||
|
*/
|
||||||
|
private final FromElement ownerFromElement;
|
||||||
|
|
||||||
|
private final CollectionPersister collectionDescriptor;
|
||||||
|
|
||||||
|
private final String collectionPropertyName;
|
||||||
|
private final String collectionPropertyPath;
|
||||||
|
private final String collectionQueryPath;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate a `CollectionPathNode`
|
||||||
|
*
|
||||||
|
* @see #from(AST, AST, HqlSqlWalker)
|
||||||
|
*/
|
||||||
|
public CollectionPathNode(
|
||||||
|
FromElement ownerFromElement,
|
||||||
|
CollectionPersister collectionDescriptor,
|
||||||
|
String collectionPropertyName,
|
||||||
|
String collectionQueryPath,
|
||||||
|
String collectionPropertyPath) {
|
||||||
|
this.ownerFromElement = ownerFromElement;
|
||||||
|
this.collectionDescriptor = collectionDescriptor;
|
||||||
|
this.collectionPropertyName = collectionPropertyName;
|
||||||
|
this.collectionQueryPath = collectionQueryPath;
|
||||||
|
this.collectionPropertyPath = collectionPropertyPath;
|
||||||
|
|
||||||
|
super.setType( SqlASTFactory.COLL_PATH );
|
||||||
|
super.setDataType( collectionDescriptor.getCollectionType() );
|
||||||
|
super.setText( collectionDescriptor.getRole() );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory for `CollectionPathNode` instances
|
||||||
|
*
|
||||||
|
* @param qualifier The left-hand-side of a dot-ident node - may be null to indicate an ident arg
|
||||||
|
* @param reference The right-hand-side of the dot-ident or the ident that is an unqualified reference
|
||||||
|
*/
|
||||||
|
public static CollectionPathNode from(
|
||||||
|
AST qualifier,
|
||||||
|
AST reference,
|
||||||
|
HqlSqlWalker walker) {
|
||||||
|
|
||||||
|
final String referenceName = reference.getText();
|
||||||
|
final String qualifierQueryPath = qualifier == null
|
||||||
|
? ""
|
||||||
|
: ( (FromReferenceNode) qualifier ).getPath();
|
||||||
|
final String referencePath = qualifier == null
|
||||||
|
? referenceName
|
||||||
|
: qualifierQueryPath + "." + reference;
|
||||||
|
|
||||||
|
if ( qualifier == null ) {
|
||||||
|
// If there is no qualifier it means the argument to `size()` was a simple IDENT node as opposed to a DOT-IDENT
|
||||||
|
// node. In this case, `reference` could technically be a join alias. This is not JPA
|
||||||
|
// compliant, but is a Hibernate-specific extension
|
||||||
|
|
||||||
|
// size( cu )
|
||||||
|
|
||||||
|
final FromElement byAlias = walker.getCurrentFromClause().getFromElement( referenceName );
|
||||||
|
|
||||||
|
if ( byAlias != null ) {
|
||||||
|
final FromElement ownerRef = byAlias.getOrigin();
|
||||||
|
final QueryableCollection collectionDescriptor = byAlias.getQueryableCollection();
|
||||||
|
|
||||||
|
return new CollectionPathNode(
|
||||||
|
ownerRef,
|
||||||
|
collectionDescriptor,
|
||||||
|
referenceName,
|
||||||
|
referencePath,
|
||||||
|
referenceName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// we (should) have an unqualified plural-attribute name - look through all of the defined from-elements
|
||||||
|
// and look for one that exposes that property
|
||||||
|
|
||||||
|
//noinspection unchecked
|
||||||
|
final List<FromElement> fromElements = walker.getCurrentFromClause().getExplicitFromElements();
|
||||||
|
|
||||||
|
if ( fromElements.size() == 1 ) {
|
||||||
|
final FromElement ownerRef = fromElements.get( 0 );
|
||||||
|
|
||||||
|
final PropertyMapping collectionPropertyMapping = ownerRef.getPropertyMapping( referenceName );
|
||||||
|
|
||||||
|
//noinspection RedundantClassCall
|
||||||
|
if ( ! CollectionType.class.isInstance( collectionPropertyMapping.getType() ) ) {
|
||||||
|
throw new QueryException( "Could not resolve identifier `" + referenceName + "` as plural-attribute" );
|
||||||
|
}
|
||||||
|
|
||||||
|
final CollectionType collectionType = (CollectionType) collectionPropertyMapping.getType();
|
||||||
|
|
||||||
|
return new CollectionPathNode(
|
||||||
|
ownerRef,
|
||||||
|
walker.getSessionFactoryHelper().requireQueryableCollection( collectionType.getRole() ),
|
||||||
|
referenceName,
|
||||||
|
referencePath,
|
||||||
|
referenceName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
FromElement discoveredQualifier = null;
|
||||||
|
|
||||||
|
//noinspection ForLoopReplaceableByForEach
|
||||||
|
for ( int i = 0; i < fromElements.size(); i++ ) {
|
||||||
|
final FromElement fromElement = fromElements.get( i );
|
||||||
|
try {
|
||||||
|
final PropertyMapping propertyMapping = fromElement.getPropertyMapping( referenceName );
|
||||||
|
//noinspection RedundantClassCall
|
||||||
|
if ( ! CollectionType.class.isInstance( propertyMapping.getType() ) ) {
|
||||||
|
throw new QueryException( "Could not resolve identifier `" + referenceName + "` as plural-attribute" );
|
||||||
|
}
|
||||||
|
|
||||||
|
discoveredQualifier = fromElement;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// try the next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( discoveredQualifier == null ) {
|
||||||
|
throw new QueryException( "Could not resolve identifier `" + referenceName + "` as plural-attribute" );
|
||||||
|
}
|
||||||
|
|
||||||
|
final FromElement ownerRef = discoveredQualifier;
|
||||||
|
|
||||||
|
final PropertyMapping collectionPropertyMapping = ownerRef.getPropertyMapping( referenceName );
|
||||||
|
|
||||||
|
//noinspection RedundantClassCall
|
||||||
|
if ( ! CollectionType.class.isInstance( collectionPropertyMapping.getType() ) ) {
|
||||||
|
throw new QueryException( "Could not resolve identifier `" + referenceName + "` as plural-attribute" );
|
||||||
|
}
|
||||||
|
|
||||||
|
final CollectionType collectionType = (CollectionType) collectionPropertyMapping.getType();
|
||||||
|
|
||||||
|
return new CollectionPathNode(
|
||||||
|
ownerRef,
|
||||||
|
walker.getSessionFactoryHelper().requireQueryableCollection( collectionType.getRole() ),
|
||||||
|
referenceName,
|
||||||
|
referencePath,
|
||||||
|
referenceName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// we have a dot-ident structure
|
||||||
|
final FromReferenceNode qualifierFromReferenceNode = (FromReferenceNode) qualifier;
|
||||||
|
try {
|
||||||
|
qualifierFromReferenceNode.resolve( false, false );
|
||||||
|
}
|
||||||
|
catch (SemanticException e) {
|
||||||
|
throw new QueryException( "Unable to resolve collection-path qualifier : " + qualifier.getText(), e );
|
||||||
|
}
|
||||||
|
|
||||||
|
final Type qualifierType = qualifierFromReferenceNode.getDataType();
|
||||||
|
final FromElement ownerRef = ( (FromReferenceNode) qualifier ).getFromElement();
|
||||||
|
|
||||||
|
final CollectionType collectionType;
|
||||||
|
final String mappedPath;
|
||||||
|
|
||||||
|
if ( qualifierType instanceof CompositeType ) {
|
||||||
|
final CompositeType qualifierCompositeType = (CompositeType) qualifierType;
|
||||||
|
final int collectionPropertyIndex = (qualifierCompositeType).getPropertyIndex( referenceName );
|
||||||
|
collectionType = (CollectionType) qualifierCompositeType.getSubtypes()[collectionPropertyIndex];
|
||||||
|
|
||||||
|
if ( ownerRef instanceof ComponentJoin ) {
|
||||||
|
mappedPath = ( (ComponentJoin) ownerRef ).getComponentPath() + "." + referenceName;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mappedPath = qualifierQueryPath.substring( qualifierQueryPath.indexOf( "." ) + 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( qualifierType instanceof EntityType ) {
|
||||||
|
final EntityType qualifierEntityType = (EntityType) qualifierType;
|
||||||
|
final String entityName = qualifierEntityType.getAssociatedEntityName();
|
||||||
|
final EntityPersister entityPersister = walker.getSessionFactoryHelper().findEntityPersisterByName( entityName );
|
||||||
|
final int propertyIndex = entityPersister.getEntityMetamodel().getPropertyIndex( referenceName );
|
||||||
|
collectionType = (CollectionType) entityPersister.getPropertyTypes()[ propertyIndex ];
|
||||||
|
mappedPath = referenceName;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new QueryException( "Unexpected collection-path reference qualifier type : " + qualifier );
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CollectionPathNode(
|
||||||
|
( (FromReferenceNode) qualifier ).getFromElement(),
|
||||||
|
walker.getSessionFactoryHelper().requireQueryableCollection( collectionType.getRole() ),
|
||||||
|
referenceName,
|
||||||
|
referencePath,
|
||||||
|
mappedPath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FromElement getCollectionOwnerRef() {
|
||||||
|
return ownerFromElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionPersister getCollectionDescriptor() {
|
||||||
|
return collectionDescriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCollectionPropertyName() {
|
||||||
|
return collectionPropertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCollectionPropertyPath() {
|
||||||
|
return collectionPropertyPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCollectionQueryPath() {
|
||||||
|
return collectionQueryPath;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,207 @@
|
||||||
|
/*
|
||||||
|
* 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.hql.internal.ast.tree;
|
||||||
|
|
||||||
|
import org.hibernate.AssertionFailure;
|
||||||
|
import org.hibernate.hql.internal.NameGenerator;
|
||||||
|
import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
|
||||||
|
import org.hibernate.hql.internal.ast.HqlSqlWalker;
|
||||||
|
import org.hibernate.internal.util.StringHelper;
|
||||||
|
import org.hibernate.persister.collection.CollectionPropertyMapping;
|
||||||
|
import org.hibernate.persister.collection.CollectionPropertyNames;
|
||||||
|
import org.hibernate.persister.collection.QueryableCollection;
|
||||||
|
import org.hibernate.persister.entity.Joinable;
|
||||||
|
import org.hibernate.type.StandardBasicTypes;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
|
import antlr.SemanticException;
|
||||||
|
import antlr.collections.AST;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class CollectionSizeNode extends SqlNode implements SelectExpression {
|
||||||
|
private static final Logger log = Logger.getLogger( CollectionSizeNode.class );
|
||||||
|
|
||||||
|
private final CollectionPathNode collectionPathNode;
|
||||||
|
private final CollectionPropertyMapping collectionPropertyMapping;
|
||||||
|
|
||||||
|
private final HqlSqlWalker walker;
|
||||||
|
private String alias;
|
||||||
|
|
||||||
|
public CollectionSizeNode(CollectionPathNode collectionPathNode, HqlSqlWalker walker) {
|
||||||
|
this.collectionPathNode = collectionPathNode;
|
||||||
|
this.walker = walker;
|
||||||
|
|
||||||
|
this.collectionPropertyMapping = new CollectionPropertyMapping( (QueryableCollection) collectionPathNode.getCollectionDescriptor() );
|
||||||
|
|
||||||
|
setType( HqlSqlTokenTypes.COLL_SIZE );
|
||||||
|
setDataType( StandardBasicTypes.INTEGER );
|
||||||
|
setText( "collection-size" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollectionPathNode getCollectionPathNode() {
|
||||||
|
return collectionPathNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HqlSqlWalker getWalker() {
|
||||||
|
return walker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toSqlExpression() throws SemanticException {
|
||||||
|
// generate subquery in the form:
|
||||||
|
//
|
||||||
|
// select count( alias_.<collection-size-columns> )
|
||||||
|
// from <collection-table> as alias_
|
||||||
|
// where <owner-key-column> = alias_.<collection-key-column>
|
||||||
|
|
||||||
|
// need:
|
||||||
|
// <collection-size-columns> => QueryableCollection#getKeyColumnNames
|
||||||
|
// <collection-key-column> => QueryableCollection#getKeyColumnNames
|
||||||
|
// <collection-table> => QueryableCollection#getTableName
|
||||||
|
// <owner-key-column> => ???
|
||||||
|
|
||||||
|
|
||||||
|
final FromElement collectionOwnerFromElement = collectionPathNode.getCollectionOwnerRef();
|
||||||
|
final QueryableCollection collectionDescriptor = (QueryableCollection) collectionPathNode.getCollectionDescriptor();
|
||||||
|
final String collectionPropertyName = collectionPathNode.getCollectionPropertyName();
|
||||||
|
|
||||||
|
getWalker().addQuerySpaces( collectionDescriptor.getCollectionSpaces() );
|
||||||
|
|
||||||
|
// silly : need to prime `SessionFactoryHelper#collectionPropertyMappingByRole`
|
||||||
|
walker.getSessionFactoryHelper().requireQueryableCollection( collectionDescriptor.getRole() );
|
||||||
|
|
||||||
|
// owner-key
|
||||||
|
final String[] ownerKeyColumns;
|
||||||
|
final AST ast = walker.getAST();
|
||||||
|
final String ownerTableAlias;
|
||||||
|
if ( ast instanceof DeleteStatement || ast instanceof UpdateStatement ) {
|
||||||
|
ownerTableAlias = collectionOwnerFromElement.getTableName();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ownerTableAlias = collectionOwnerFromElement.getTableAlias();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String lhsPropertyName = collectionDescriptor.getCollectionType().getLHSPropertyName();
|
||||||
|
if ( lhsPropertyName == null ) {
|
||||||
|
ownerKeyColumns = StringHelper.qualify(
|
||||||
|
ownerTableAlias,
|
||||||
|
( (Joinable) collectionDescriptor.getOwnerEntityPersister() ).getKeyColumnNames()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ownerKeyColumns = collectionOwnerFromElement.toColumns( ownerTableAlias, lhsPropertyName, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
// collection-key
|
||||||
|
final String collectionTableAlias = collectionOwnerFromElement.getFromClause()
|
||||||
|
.getAliasGenerator()
|
||||||
|
.createName( collectionPathNode.getCollectionPropertyName() );
|
||||||
|
final String[] collectionKeyColumns = StringHelper.qualify( collectionTableAlias, collectionDescriptor.getKeyColumnNames() );
|
||||||
|
|
||||||
|
|
||||||
|
if ( collectionKeyColumns.length != ownerKeyColumns.length ) {
|
||||||
|
throw new AssertionFailure( "Mismatch between collection key columns" );
|
||||||
|
}
|
||||||
|
|
||||||
|
// PropertyMapping(c).toColumns(customers)
|
||||||
|
// PropertyMapping(c.customers).toColumns(SIZE)
|
||||||
|
|
||||||
|
// size expression (the count function)
|
||||||
|
final String[] sizeColumns = this.collectionPropertyMapping.toColumns(
|
||||||
|
collectionTableAlias,
|
||||||
|
CollectionPropertyNames.COLLECTION_SIZE
|
||||||
|
);
|
||||||
|
assert sizeColumns.length == 1;
|
||||||
|
final String sizeColumn = sizeColumns[0];
|
||||||
|
|
||||||
|
final StringBuilder buffer = new StringBuilder( "(select " ).append( sizeColumn );
|
||||||
|
buffer.append( " from " ).append( collectionDescriptor.getTableName() ).append( " as " ).append( collectionTableAlias );
|
||||||
|
buffer.append( " where " );
|
||||||
|
|
||||||
|
boolean firstPass = true;
|
||||||
|
for ( int i = 0; i < ownerKeyColumns.length; i++ ) {
|
||||||
|
if ( firstPass ) {
|
||||||
|
firstPass = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer.append( " and " );
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.append( ownerKeyColumns[i] ).append( " = " ).append( collectionKeyColumns[i] );
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.append( ")" );
|
||||||
|
|
||||||
|
if ( scalarName != null ) {
|
||||||
|
buffer.append( " as " ).append( scalarName );
|
||||||
|
}
|
||||||
|
|
||||||
|
final String subQuery = buffer.toString();
|
||||||
|
|
||||||
|
log.debugf(
|
||||||
|
"toSqlExpression( size(%s) ) -> %s",
|
||||||
|
collectionPathNode.getCollectionQueryPath(),
|
||||||
|
subQuery
|
||||||
|
);
|
||||||
|
|
||||||
|
return subQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String scalarName;
|
||||||
|
|
||||||
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
// SelectExpression
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScalarColumnText(int i) throws SemanticException {
|
||||||
|
log.debugf( "setScalarColumnText(%s)", i );
|
||||||
|
scalarName = NameGenerator.scalarName( i, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScalarColumn(int i) throws SemanticException {
|
||||||
|
log.debugf( "setScalarColumn(%s)", i );
|
||||||
|
setScalarColumnText( i );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScalarColumnIndex() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FromElement getFromElement() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConstructor() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReturnableEntity() throws SemanticException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScalar() throws SemanticException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlias(String alias) {
|
||||||
|
this.alias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlias() {
|
||||||
|
return alias;
|
||||||
|
}
|
||||||
|
}
|
|
@ -152,6 +152,9 @@ public class ComponentJoin extends FromElement {
|
||||||
return getComponentPath() + '.' + propertyName;
|
return getComponentPath() + '.' + propertyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `size( c.component.customers )`
|
||||||
|
// PropertyMapping(c).toColumns( component.customers )
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] toColumns(String alias, String propertyName) throws QueryException {
|
public String[] toColumns(String alias, String propertyName) throws QueryException {
|
||||||
return getBasePropertyMapping().toColumns( alias, getPropertyPath( propertyName ) );
|
return getBasePropertyMapping().toColumns( alias, getPropertyPath( propertyName ) );
|
||||||
|
|
|
@ -97,6 +97,10 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FromElementType getElementType() {
|
||||||
|
return elementType;
|
||||||
|
}
|
||||||
|
|
||||||
protected void initializeComponentJoin(FromElementType elementType) {
|
protected void initializeComponentJoin(FromElementType elementType) {
|
||||||
fromClause.registerFromElement( this );
|
fromClause.registerFromElement( this );
|
||||||
elementType.applyTreatAsDeclarations( getWalker().getTreatAsDeclarationsByPath( classAlias ) );
|
elementType.applyTreatAsDeclarations( getWalker().getTreatAsDeclarationsByPath( classAlias ) );
|
||||||
|
|
|
@ -23,6 +23,21 @@ public class ImpliedFromElement extends FromElement {
|
||||||
*/
|
*/
|
||||||
private boolean inProjectionList;
|
private boolean inProjectionList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here to add debug breakpoints
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public ImpliedFromElement() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Here to add debug breakpoints
|
||||||
|
*/
|
||||||
|
public ImpliedFromElement(FromClause fromClause, FromElement origin, String alias) {
|
||||||
|
super( fromClause, origin, alias );
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isImplied() {
|
public boolean isImplied() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,11 +154,13 @@ public class MethodNode extends AbstractSelectExpression implements FunctionNode
|
||||||
selectColumns = cpr.toColumns( fromElement.getTableAlias() );
|
selectColumns = cpr.toColumns( fromElement.getTableAlias() );
|
||||||
|
|
||||||
// setDataType( fromElement.getPropertyType( propertyName, propertyName ) );
|
// setDataType( fromElement.getPropertyType( propertyName, propertyName ) );
|
||||||
selectColumns = fromElement.toColumns( fromElement.getTableAlias(), propertyName, inSelect );
|
// selectColumns = fromElement.toColumns( fromElement.getTableAlias(), propertyName, inSelect );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( collectionNode instanceof DotNode ) {
|
if ( collectionNode instanceof DotNode ) {
|
||||||
prepareAnyImplicitJoins( (DotNode) collectionNode );
|
prepareAnyImplicitJoins( (DotNode) collectionNode );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !inSelect ) {
|
if ( !inSelect ) {
|
||||||
fromElement.setText( "" );
|
fromElement.setText( "" );
|
||||||
fromElement.setUseWhereFragment( false );
|
fromElement.setUseWhereFragment( false );
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.junit.Test;
|
||||||
|
|
||||||
import org.hibernate.testing.TestForIssue;
|
import org.hibernate.testing.TestForIssue;
|
||||||
import org.hibernate.testing.transaction.TransactionUtil;
|
import org.hibernate.testing.transaction.TransactionUtil;
|
||||||
|
import org.hibernate.testing.transaction.TransactionUtil2;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
@ -96,6 +97,18 @@ public class ComponentInWhereClauseTest extends BaseEntityManagerFunctionalTestC
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSizeExpressionForTheOneToManyPropertyOfAComponentHql() {
|
||||||
|
TransactionUtil2.inTransaction(
|
||||||
|
entityManagerFactory(),
|
||||||
|
session -> {
|
||||||
|
final String hql = "from Employee e where size( e.projects.previousProjects ) = 2";
|
||||||
|
final List resultsList = session.createQuery( hql ).list();
|
||||||
|
assertThat( resultsList.size(), is( 1 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSizeExpressionForTheElementCollectionPropertyOfAComponent() {
|
public void testSizeExpressionForTheElementCollectionPropertyOfAComponent() {
|
||||||
TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> {
|
TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> {
|
||||||
|
|
|
@ -60,6 +60,7 @@ import org.hibernate.testing.FailureExpected;
|
||||||
import org.hibernate.testing.RequiresDialectFeature;
|
import org.hibernate.testing.RequiresDialectFeature;
|
||||||
import org.hibernate.testing.SkipForDialect;
|
import org.hibernate.testing.SkipForDialect;
|
||||||
import org.hibernate.testing.TestForIssue;
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import antlr.RecognitionException;
|
import antlr.RecognitionException;
|
||||||
|
@ -571,6 +572,7 @@ public class HQLTest extends QueryTranslatorTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Ignore( "Old parser generated incorrect SQL for `size()`")
|
||||||
public void testSizeFunctionAndProperty() {
|
public void testSizeFunctionAndProperty() {
|
||||||
assertTranslation("from Animal a where a.offspring.size > 0");
|
assertTranslation("from Animal a where a.offspring.size > 0");
|
||||||
assertTranslation("from Animal a join a.offspring where a.offspring.size > 1");
|
assertTranslation("from Animal a join a.offspring where a.offspring.size > 1");
|
||||||
|
@ -624,16 +626,9 @@ public class HQLTest extends QueryTranslatorTestCase {
|
||||||
assertTranslation( "from Simple s where s = some( select sim from Simple sim where sim.other.count=s.other.count )" );
|
assertTranslation( "from Simple s where s = some( select sim from Simple sim where sim.other.count=s.other.count )" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCollectionOfValuesSize() throws Exception {
|
|
||||||
//SQL *was* missing a comma
|
|
||||||
assertTranslation( "select size(baz.stringDateMap) from org.hibernate.test.legacy.Baz baz" );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCollectionFunctions() throws Exception {
|
public void testCollectionFunctions() throws Exception {
|
||||||
//these are both broken, a join that belongs in the subselect finds its way into the main query
|
//these are both broken, a join that belongs in the subselect finds its way into the main query
|
||||||
assertTranslation( "from Zoo zoo where size(zoo.animals) > 100" );
|
|
||||||
assertTranslation( "from Zoo zoo where maxindex(zoo.mammals) = 'dog'" );
|
assertTranslation( "from Zoo zoo where maxindex(zoo.mammals) = 'dog'" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,8 +646,10 @@ public class HQLTest extends QueryTranslatorTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCollectionSize() throws Exception {
|
@Ignore( "The old parser generated incorrect SQL for selection of size functions" )
|
||||||
|
public void testCollectionSizeSelection() throws Exception {
|
||||||
assertTranslation( "select size(zoo.animals) from Zoo zoo" );
|
assertTranslation( "select size(zoo.animals) from Zoo zoo" );
|
||||||
|
assertTranslation( "select size(baz.stringDateMap) from org.hibernate.test.legacy.Baz baz" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -31,7 +31,49 @@ import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||||
public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase {
|
public class ManyToManySizeTest extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSizeAsSelectExpression() {
|
public void testSizeAsRestriction() {
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory,
|
||||||
|
session -> {
|
||||||
|
final List results = session.createQuery(
|
||||||
|
"select c.id from Company c where size( c.customers ) = 0"
|
||||||
|
).list();
|
||||||
|
assertThat( results.size(), is( 1 ) );
|
||||||
|
assertThat( results.get( 0 ), is( 0 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSizeAsCompoundSelectExpression() {
|
||||||
|
doInHibernate(
|
||||||
|
this::sessionFactory,
|
||||||
|
session -> {
|
||||||
|
final List results = session.createQuery(
|
||||||
|
"select c.id, c.name, size( c.customers )" +
|
||||||
|
" from Company c" +
|
||||||
|
" group by c.id, c.name" +
|
||||||
|
" order by c.id"
|
||||||
|
).list();
|
||||||
|
assertThat( results.size(), is( 3 ) );
|
||||||
|
|
||||||
|
final Object[] first = (Object[]) results.get( 0 );
|
||||||
|
assertThat( first[ 0 ], is( 0 ) );
|
||||||
|
assertThat( first[ 2 ], is( 0 ) );
|
||||||
|
|
||||||
|
final Object[] second = (Object[]) results.get( 1 );
|
||||||
|
assertThat( second[ 0 ], is( 1 ) );
|
||||||
|
assertThat( second[ 2 ], is( 1 ) );
|
||||||
|
|
||||||
|
final Object[] third = (Object[]) results.get( 2 );
|
||||||
|
assertThat( third[ 0 ], is( 2 ) );
|
||||||
|
assertThat( third[ 2 ], is( 2 ) );
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSizeAsCtorSelectExpression() {
|
||||||
doInHibernate(
|
doInHibernate(
|
||||||
this::sessionFactory,
|
this::sessionFactory,
|
||||||
session -> {
|
session -> {
|
||||||
|
|
Loading…
Reference in New Issue