Author: steve.ebersole(a)jboss.com
Date: 2009-11-18 16:02:19 -0500 (Wed, 18 Nov 2009)
New Revision: 18006
Added:
core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/ComponentJoin.java
core/trunk/entitymanager/src/test/java/org/hibernate/ejb/test/ql/
core/trunk/entitymanager/src/test/java/org/hibernate/ejb/test/ql/ComponentJoinsTest.java
Modified:
core/trunk/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java
core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElement.java
core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElementFactory.java
core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElementType.java
Log:
HHH-4584 - Query Language needs to support joins on embedded values
Modified: core/trunk/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java 2009-11-18
16:56:13 UTC (rev 18005)
+++ core/trunk/core/src/main/java/org/hibernate/hql/ast/HqlSqlWalker.java 2009-11-18
21:02:19 UTC (rev 18006)
@@ -69,6 +69,7 @@
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.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 @@
// 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() ) {
Added: core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/ComponentJoin.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/ComponentJoin.java
(rev 0)
+++ core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/ComponentJoin.java 2009-11-18
21:02:19 UTC (rev 18006)
@@ -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 ) );
+ }
+ }
+}
Modified: core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElement.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElement.java 2009-11-18
16:56:13 UTC (rev 18005)
+++ core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElement.java 2009-11-18
21:02:19 UTC (rev 18006)
@@ -90,6 +90,30 @@
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();
}
Modified:
core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElementFactory.java
===================================================================
---
core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElementFactory.java 2009-11-18
16:56:13 UTC (rev 18005)
+++
core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElementFactory.java 2009-11-18
21:02:19 UTC (rev 18006)
@@ -39,6 +39,7 @@
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 @@
*
* @author josh
*/
-class FromElementFactory implements SqlTokenTypes {
+public class FromElementFactory implements SqlTokenTypes {
private static final Logger log = LoggerFactory.getLogger( FromElementFactory.class );
@@ -295,6 +296,12 @@
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;
Modified: core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElementType.java
===================================================================
---
core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElementType.java 2009-11-18
16:56:13 UTC (rev 18005)
+++
core/trunk/core/src/main/java/org/hibernate/hql/ast/tree/FromElementType.java 2009-11-18
21:02:19 UTC (rev 18006)
@@ -74,6 +74,10 @@
}
}
+ protected FromElementType(FromElement fromElement) {
+ this.fromElement = fromElement;
+ }
+
private String getTableAlias() {
return fromElement.getTableAlias();
}
Added:
core/trunk/entitymanager/src/test/java/org/hibernate/ejb/test/ql/ComponentJoinsTest.java
===================================================================
---
core/trunk/entitymanager/src/test/java/org/hibernate/ejb/test/ql/ComponentJoinsTest.java
(rev 0)
+++
core/trunk/entitymanager/src/test/java/org/hibernate/ejb/test/ql/ComponentJoinsTest.java 2009-11-18
21:02:19 UTC (rev 18006)
@@ -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();
+ }
+}