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.UpdateStatement;
|
||||||
import org.hibernate.hql.ast.tree.OperatorNode;
|
import org.hibernate.hql.ast.tree.OperatorNode;
|
||||||
import org.hibernate.hql.ast.tree.ParameterContainer;
|
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.ASTPrinter;
|
||||||
import org.hibernate.hql.ast.util.ASTUtil;
|
import org.hibernate.hql.ast.util.ASTUtil;
|
||||||
import org.hibernate.hql.ast.util.AliasGenerator;
|
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.Type;
|
||||||
import org.hibernate.type.VersionType;
|
import org.hibernate.type.VersionType;
|
||||||
import org.hibernate.type.DbTimestampType;
|
import org.hibernate.type.DbTimestampType;
|
||||||
|
import org.hibernate.type.ComponentType;
|
||||||
import org.hibernate.usertype.UserVersionType;
|
import org.hibernate.usertype.UserVersionType;
|
||||||
import org.hibernate.util.ArrayHelper;
|
import org.hibernate.util.ArrayHelper;
|
||||||
import org.hibernate.util.StringHelper;
|
import org.hibernate.util.StringHelper;
|
||||||
|
@ -362,7 +364,21 @@ 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
|
// Generate an explicit join for the root dot node. The implied joins will be collected and passed up
|
||||||
// to the root dot node.
|
// to the root dot node.
|
||||||
dot.resolve( true, false, alias == null ? null : alias.getText() );
|
dot.resolve( true, false, alias == null ? null : alias.getText() );
|
||||||
FromElement fromElement = dot.getImpliedJoin();
|
|
||||||
|
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 );
|
fromElement.setAllPropertyFetch( propertyFetch != null );
|
||||||
|
|
||||||
if ( with != null ) {
|
if ( with != null ) {
|
||||||
|
@ -371,6 +387,7 @@ public class HqlSqlWalker extends HqlSqlBaseWalker implements ErrorReporter, Par
|
||||||
}
|
}
|
||||||
handleWithFragment( fromElement, with );
|
handleWithFragment( fromElement, with );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ( log.isDebugEnabled() ) {
|
if ( log.isDebugEnabled() ) {
|
||||||
log.debug( "createFromJoinElement() : " + getASTPrinter().showAsString( fromElement, "-- join tree --" ) );
|
log.debug( "createFromJoinElement() : " + getASTPrinter().showAsString( fromElement, "-- join tree --" ) );
|
||||||
|
|
|
@ -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() {
|
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() {
|
public String getCollectionSuffix() {
|
||||||
return elementType.getCollectionSuffix();
|
return elementType.getCollectionSuffix();
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.hibernate.type.AssociationType;
|
||||||
import org.hibernate.type.CollectionType;
|
import org.hibernate.type.CollectionType;
|
||||||
import org.hibernate.type.EntityType;
|
import org.hibernate.type.EntityType;
|
||||||
import org.hibernate.type.Type;
|
import org.hibernate.type.Type;
|
||||||
|
import org.hibernate.type.ComponentType;
|
||||||
import org.hibernate.util.StringHelper;
|
import org.hibernate.util.StringHelper;
|
||||||
|
|
||||||
import antlr.ASTFactory;
|
import antlr.ASTFactory;
|
||||||
|
@ -53,7 +54,7 @@ import org.slf4j.LoggerFactory;
|
||||||
*
|
*
|
||||||
* @author josh
|
* @author josh
|
||||||
*/
|
*/
|
||||||
class FromElementFactory implements SqlTokenTypes {
|
public class FromElementFactory implements SqlTokenTypes {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger( FromElementFactory.class );
|
private static final Logger log = LoggerFactory.getLogger( FromElementFactory.class );
|
||||||
|
|
||||||
|
@ -295,6 +296,12 @@ class FromElementFactory implements SqlTokenTypes {
|
||||||
return elem;
|
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 createElementJoin(QueryableCollection queryableCollection) throws SemanticException {
|
||||||
FromElement elem;
|
FromElement elem;
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,10 @@ class FromElementType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected FromElementType(FromElement fromElement) {
|
||||||
|
this.fromElement = fromElement;
|
||||||
|
}
|
||||||
|
|
||||||
private String getTableAlias() {
|
private String getTableAlias() {
|
||||||
return fromElement.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