HHH-4584 - Query Language needs to support joins on embedded values

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@18006 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Steve Ebersole 2009-11-18 21:02:19 +00:00
parent abc165eaba
commit 085c6c9e9e
6 changed files with 297 additions and 7 deletions

View File

@ -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,8 +364,22 @@ 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);
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 ) {
@ -371,6 +387,7 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par
}
handleWithFragment( fromElement, with );
}
}
if ( log.isDebugEnabled() ) {
log.debug( "createFromJoinElement() : " + getASTPrinter().showAsString( fromElement, "-- join tree --" ) );

View File

@ -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. <tt>... from Person p join p.name as n ...</tt>)
*
* @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 <tablealias>.<columnname> as <projectionalias>
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 ) );
}
}
}

View File

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

View File

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

View File

@ -74,6 +74,10 @@ class FromElementType {
}
}
protected FromElementType(FromElement fromElement) {
this.fromElement = fromElement;
}
private String getTableAlias() {
return fromElement.getTableAlias();
}

View File

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