diff --git a/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java b/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java index e88094a393..70e6529745 100644 --- a/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java +++ b/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java @@ -69,6 +69,7 @@ import org.hibernate.hql.ast.tree.SelectExpression; import org.hibernate.hql.ast.tree.UpdateStatement; import org.hibernate.hql.ast.tree.OperatorNode; import org.hibernate.hql.ast.tree.ParameterContainer; +import org.hibernate.hql.ast.tree.FromElementFactory; import org.hibernate.hql.ast.util.ASTPrinter; import org.hibernate.hql.ast.util.ASTUtil; import org.hibernate.hql.ast.util.AliasGenerator; @@ -92,6 +93,7 @@ import org.hibernate.type.AssociationType; import org.hibernate.type.Type; import org.hibernate.type.VersionType; import org.hibernate.type.DbTimestampType; +import org.hibernate.type.ComponentType; import org.hibernate.usertype.UserVersionType; import org.hibernate.util.ArrayHelper; import org.hibernate.util.StringHelper; @@ -362,14 +364,29 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par // Generate an explicit join for the root dot node. The implied joins will be collected and passed up // to the root dot node. dot.resolve( true, false, alias == null ? null : alias.getText() ); - FromElement fromElement = dot.getImpliedJoin(); - fromElement.setAllPropertyFetch(propertyFetch!=null); - if ( with != null ) { - if ( fetch ) { - throw new SemanticException( "with-clause not allowed on fetched associations; use filters" ); + final FromElement fromElement; + if ( dot.getDataType() != null && dot.getDataType().isComponentType() ) { + FromElementFactory factory = new FromElementFactory( + getCurrentFromClause(), + dot.getLhs().getFromElement(), + dot.getPropertyPath(), + alias == null ? null : alias.getText(), + null, + false + ); + fromElement = factory.createComponentJoin( (ComponentType) dot.getDataType() ); + } + else { + fromElement = dot.getImpliedJoin(); + fromElement.setAllPropertyFetch( propertyFetch != null ); + + if ( with != null ) { + if ( fetch ) { + throw new SemanticException( "with-clause not allowed on fetched associations; use filters" ); + } + handleWithFragment( fromElement, with ); } - handleWithFragment( fromElement, with ); } if ( log.isDebugEnabled() ) { diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/ComponentJoin.java b/core/src/main/java/org/hibernate/hql/ast/tree/ComponentJoin.java new file mode 100644 index 0000000000..ad4a056788 --- /dev/null +++ b/core/src/main/java/org/hibernate/hql/ast/tree/ComponentJoin.java @@ -0,0 +1,181 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009 by Red Hat Inc and/or its affiliates or by + * third-party contributors as indicated by either @author tags or express + * copyright attribution statements applied by the authors. All + * third-party contributions are distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.hql.ast.tree; + +import org.hibernate.type.ComponentType; +import org.hibernate.type.Type; +import org.hibernate.persister.collection.QueryableCollection; +import org.hibernate.persister.entity.PropertyMapping; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.QueryException; +import org.hibernate.util.StringHelper; +import org.hibernate.hql.NameGenerator; + +/** + * Models an explicit join terminating at a component value (e.g. ... from Person p join p.name as n ...) + * + * @author Steve Ebersole + */ +public class ComponentJoin extends FromElement { + private final String componentPath; + private final ComponentType componentType; + + private final String componentProperty; + private final String columns; + + public ComponentJoin( + FromClause fromClause, + FromElement origin, + String alias, + String componentPath, + ComponentType componentType) { + super( fromClause, origin, alias ); + this.componentPath = componentPath; + this.componentType = componentType; + this.componentProperty = StringHelper.unqualify( componentPath ); + fromClause.addJoinByPathMap( componentPath, this ); + initializeComponentJoin( new ComponentFromElementType( this ) ); + + final String[] cols = origin.getPropertyMapping( "" ).toColumns( getTableAlias(), componentProperty ); + StringBuffer buf = new StringBuffer(); + for ( int j = 0; j < cols.length; j++ ) { + final String column = cols[j]; + if ( j > 0 ) { + buf.append( ", " ); + } + buf.append( column ); + } + this.columns = buf.toString(); + } + + public String getComponentPath() { + return componentPath; + } + + public String getComponentProperty() { + return componentProperty; + } + + public ComponentType getComponentType() { + return componentType; + } + + + public Type getDataType() { + return getComponentType(); + } + + /** + * {@inheritDoc} + */ + public String getIdentityColumn() { + // used to "resolve" the IdentNode when our alias is encountered *by itself* in the query; so + // here we use the component + // NOTE : ^^ is true *except for* when encountered by itself in the SELECT clause. That gets + // routed through org.hibernate.hql.ast.tree.ComponentJoin.ComponentFromElementType.renderScalarIdentifierSelect() + // which we also override to account for + return columns; + } + + /** + * {@inheritDoc} + */ + public String getDisplayText() { + return "ComponentJoin{path=" + getComponentPath() + ", type=" + componentType.getReturnedClass() + "}"; + } + + public class ComponentFromElementType extends FromElementType { + private final PropertyMapping propertyMapping = new ComponentPropertyMapping(); + + public ComponentFromElementType(FromElement fromElement) { + super( fromElement ); + } + + public Type getDataType() { + return getComponentType(); + } + + /** + * {@inheritDoc} + */ + public QueryableCollection getQueryableCollection() { + return null; + } + + /** + * {@inheritDoc} + */ + public PropertyMapping getPropertyMapping(String propertyName) { + return propertyMapping; + } + + /** + * {@inheritDoc} + */ + public Type getPropertyType(String propertyName, String propertyPath) { + int index = getComponentType().getPropertyIndex( propertyName ); + return getComponentType().getSubtypes()[index]; + } + + public String renderScalarIdentifierSelect(int i) { + String[] cols = getBasePropertyMapping().toColumns( getTableAlias(), getComponentProperty() ); + StringBuffer buf = new StringBuffer(); + // For property references generate . as + for ( int j = 0; j < cols.length; j++ ) { + final String column = cols[j]; + if ( j > 0 ) { + buf.append( ", " ); + } + buf.append( column ).append( " as " ).append( NameGenerator.scalarName( i, j ) ); + } + return buf.toString(); + } + } + + protected PropertyMapping getBasePropertyMapping() { + return getOrigin().getPropertyMapping( "" ); + } + + private final class ComponentPropertyMapping implements PropertyMapping { + public Type getType() { + return getComponentType(); + } + + public Type toType(String propertyName) throws QueryException { + return getBasePropertyMapping().toType( getPropertyPath( propertyName ) ); + } + + protected String getPropertyPath(String propertyName) { + return getComponentPath() + '.' + propertyName; + } + + public String[] toColumns(String alias, String propertyName) throws QueryException { + return getBasePropertyMapping().toColumns( alias, getPropertyPath( propertyName ) ); + } + + public String[] toColumns(String propertyName) throws QueryException, UnsupportedOperationException { + return getBasePropertyMapping().toColumns( getPropertyPath( propertyName ) ); + } + } +} diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/FromElement.java b/core/src/main/java/org/hibernate/hql/ast/tree/FromElement.java index 39b5f54772..a5831853e2 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/FromElement.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/FromElement.java @@ -90,6 +90,30 @@ public class FromElement extends HqlSqlWalkerNode implements DisplayableNode, Pa public FromElement() { } + /** + * Constructor form used to initialize {@link ComponentJoin} + * + * @param fromClause The FROM clause to which this element belongs + * @param origin The origin (LHS) of this element + * @param alias The alias applied to this element + */ + protected FromElement( + FromClause fromClause, + FromElement origin, + String alias) { + this.fromClause = fromClause; + this.origin = origin; + this.classAlias = alias; + this.tableAlias = origin.getTableAlias(); + super.initialize( fromClause.getWalker() ); + } + + protected void initializeComponentJoin(FromElementType elementType) { + this.elementType = elementType; + fromClause.registerFromElement( this ); + initialized = true; + } + public String getCollectionSuffix() { return elementType.getCollectionSuffix(); } diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/FromElementFactory.java b/core/src/main/java/org/hibernate/hql/ast/tree/FromElementFactory.java index ed1ea19c63..b3c6fa2cc0 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/FromElementFactory.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/FromElementFactory.java @@ -39,6 +39,7 @@ import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; +import org.hibernate.type.ComponentType; import org.hibernate.util.StringHelper; import antlr.ASTFactory; @@ -53,7 +54,7 @@ import org.slf4j.LoggerFactory; * * @author josh */ -class FromElementFactory implements SqlTokenTypes { +public class FromElementFactory implements SqlTokenTypes { private static final Logger log = LoggerFactory.getLogger( FromElementFactory.class ); @@ -295,6 +296,12 @@ class FromElementFactory implements SqlTokenTypes { return elem; } + public FromElement createComponentJoin(ComponentType type) { + // need to create a "place holder" from-element that can store the component/alias for this + // component join + return new ComponentJoin( fromClause, origin, classAlias, path, type ); + } + FromElement createElementJoin(QueryableCollection queryableCollection) throws SemanticException { FromElement elem; diff --git a/core/src/main/java/org/hibernate/hql/ast/tree/FromElementType.java b/core/src/main/java/org/hibernate/hql/ast/tree/FromElementType.java index d52bee0511..20705530d2 100644 --- a/core/src/main/java/org/hibernate/hql/ast/tree/FromElementType.java +++ b/core/src/main/java/org/hibernate/hql/ast/tree/FromElementType.java @@ -74,6 +74,10 @@ class FromElementType { } } + protected FromElementType(FromElement fromElement) { + this.fromElement = fromElement; + } + private String getTableAlias() { return fromElement.getTableAlias(); } diff --git a/entitymanager/src/test/java/org/hibernate/ejb/test/ql/ComponentJoinsTest.java b/entitymanager/src/test/java/org/hibernate/ejb/test/ql/ComponentJoinsTest.java new file mode 100644 index 0000000000..b61e1c77f6 --- /dev/null +++ b/entitymanager/src/test/java/org/hibernate/ejb/test/ql/ComponentJoinsTest.java @@ -0,0 +1,57 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009 by Red Hat Inc and/or its affiliates or by + * third-party contributors as indicated by either @author tags or express + * copyright attribution statements applied by the authors. All + * third-party contributions are distributed under license by Red Hat Inc. + * + * This copyrighted material is made available to anyone wishing to use, modify, + * copy, or redistribute it subject to the terms and conditions of the GNU + * Lesser General Public License, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this distribution; if not, write to: + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA\ + */ +package org.hibernate.ejb.test.ql; + +import javax.persistence.EntityManager; + +import org.hibernate.ejb.test.TestCase; +import org.hibernate.ejb.criteria.components.Client; + +/** + * Tests related to specifying joins on components (embedded values). + * + * @author Steve Ebersole + */ +public class ComponentJoinsTest extends TestCase { + public Class[] getAnnotatedClasses() { + return new Class[] { Client.class }; + } + + public void testComponentJoins() { + // Just checking proper query construction and syntax checking via database query parser... + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + // use it in WHERE + em.createQuery( "select c from Client c join c.name as n where n.lastName like '%'" ).getResultList(); + // use it in SELECT + em.createQuery( "select n.lastName from Client c join c.name as n" ).getResultList(); + em.createQuery( "select n from Client c join c.name as n" ).getResultList(); + // use it in ORDER BY + em.createQuery( "select n from Client c join c.name as n order by n.lastName" ).getResultList(); + em.createQuery( "select n from Client c join c.name as n order by c" ).getResultList(); + em.createQuery( "select n from Client c join c.name as n order by n" ).getResultList(); + em.getTransaction().commit(); + em.close(); + } +}