HHH-13619 - Support for JPA's `size` function as a select expression

- initial support
This commit is contained in:
Steve Ebersole 2020-03-04 12:06:59 -06:00
parent 24cedfa6ec
commit 692f19c83f
16 changed files with 630 additions and 11 deletions

View File

@ -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

View File

@ -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)?

View File

@ -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

View File

@ -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.
* *

View File

@ -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;

View File

@ -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" );
}
}
} }

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 ) );

View File

@ -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 ) );

View File

@ -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;
} }

View File

@ -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 );

View File

@ -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 -> {

View File

@ -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

View File

@ -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 -> {