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:
parent
abc165eaba
commit
085c6c9e9e
|
@ -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() ) {
|
||||
|
|
|
@ -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 ) );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -74,6 +74,10 @@ class FromElementType {
|
|||
}
|
||||
}
|
||||
|
||||
protected FromElementType(FromElement fromElement) {
|
||||
this.fromElement = fromElement;
|
||||
}
|
||||
|
||||
private String getTableAlias() {
|
||||
return fromElement.getTableAlias();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue