[hibernate-commits] Hibernate SVN: r20014 - in core/trunk: core/src/main/java/org/hibernate/cfg and 5 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Thu Jul 22 16:23:09 EDT 2010


Author: steve.ebersole at jboss.com
Date: 2010-07-22 16:23:08 -0400 (Thu, 22 Jul 2010)
New Revision: 20014

Added:
   core/trunk/core/src/main/java/org/hibernate/SQLQueryResultMappingBuilder.java
Modified:
   core/trunk/core/src/main/java/org/hibernate/SQLQuery.java
   core/trunk/core/src/main/java/org/hibernate/cfg/NamedSQLQuerySecondPass.java
   core/trunk/core/src/main/java/org/hibernate/classic/Session.java
   core/trunk/core/src/main/java/org/hibernate/engine/NamedSQLQueryDefinition.java
   core/trunk/core/src/main/java/org/hibernate/engine/query/sql/NativeSQLQueryReturn.java
   core/trunk/core/src/main/java/org/hibernate/impl/SQLQueryImpl.java
   core/trunk/testsuite/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java
Log:
HHH-3908 - Expose way to fully control fetching and result mapping on SQLQuery


Modified: core/trunk/core/src/main/java/org/hibernate/SQLQuery.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/SQLQuery.java	2010-07-22 19:52:43 UTC (rev 20013)
+++ core/trunk/core/src/main/java/org/hibernate/SQLQuery.java	2010-07-22 20:23:08 UTC (rev 20014)
@@ -1,10 +1,10 @@
 /*
  * Hibernate, Relational Persistence for Idiomatic Java
  *
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
  * indicated by the @author tags or express copyright attribution
  * statements applied by the authors.  All third-party contributions are
- * distributed under license by Red Hat Middleware LLC.
+ * 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
@@ -20,97 +20,323 @@
  * Free Software Foundation, Inc.
  * 51 Franklin Street, Fifth Floor
  * Boston, MA  02110-1301  USA
- *
  */
 package org.hibernate;
 
 import org.hibernate.type.Type;
 
 /**
- * Allows the user to declare the types and select list injection
- * points of all entities returned by the query. Also allows
- * declaration of the type and column alias of any scalar results
- * of the query.
+ * Represents a "native sql" query and allows the user to define certain aspects about its execution, such as:<ul>
+ * <li>result-set value mapping (see below)</li>
+ * <li>
+ * 	Tables used via {@link #addSynchronizedQuerySpace}, {@link #addSynchronizedEntityName} and
+ *  {@link #addSynchronizedEntityClass}.  This allows Hibernate to properly know how to deal with auto-flush checking
+ *  as well as cached query results if the results of the query are being cached.
+ * </li>
+ * </ul>
+ * <p/>
+ * In terms of result-set mapping, there are 3 approaches to defining:<ul>
+ * <li>If this represents a named sql query, the mapping could be associated with the query as part of its metadata</li>
+ * <li>A pre-defined (defined in metadata and named) mapping can be associated with {@link #setResultSetMapping}</li>
+ * <li>Defined locally per the various {@link #addEntity}, {@link #addRoot}, {@link #addJoin}, {@link #addFetch} and {@link #addScalar} methods</li>
+ *
+ * </ul>
  * 
  * @author Gavin King
+ * @author Steve Ebersole
  */
 public interface SQLQuery extends Query {
+
 	/**
-	 * Declare a "root" entity, without specifying an alias
+	 * Adds a query space (table name) for (a) auto-flush checking and (b) query result cache invalidation checking
+	 *
+	 * @param querySpace The query space to be auto-flushed for this query.
+	 *
+	 * @return this, for method chaining
 	 */
-	public SQLQuery addEntity(String entityName);
+	public SQLQuery addSynchronizedQuerySpace(String querySpace);
+
 	/**
-	 * Declare a "root" entity
+	 * Adds an entity name for (a) auto-flush checking and (b) query result cache invalidation checking.  Same as
+	 * {@link #addSynchronizedQuerySpace} for all tables associated with the given entity.
+	 *
+	 * @param entityName The name of the entity upon whose defined query spaces we should additionally synchronize.
+	 *
+	 * @return this, for method chaining
+	 *
+	 * @throws MappingException Indicates the given name could not be resolved as an entity
 	 */
-	public SQLQuery addEntity(String alias, String entityName);
+	public SQLQuery addSynchronizedEntityName(String entityName) throws MappingException;
+
 	/**
-	 * Declare a "root" entity, specifying a lock mode
+	 * Adds an entity for (a) auto-flush checking and (b) query result cache invalidation checking.  Same as
+	 * {@link #addSynchronizedQuerySpace} for all tables associated with the given entity.
+	 *
+	 * @param entityClass The class of the entity upon whose defined query spaces we should additionally synchronize.
+	 *
+	 * @return this, for method chaining
+	 *
+	 * @throws MappingException Indicates the given class could not be resolved as an entity
 	 */
-	public SQLQuery addEntity(String alias, String entityName, LockMode lockMode);
+	public SQLQuery addSynchronizedEntityClass(Class entityClass) throws MappingException;
+
 	/**
-	 * Declare a "root" entity, without specifying an alias
+	 * Use a predefined named result-set mapping.  This might be defined by a {@code <result-set/>} element in a
+	 * Hibernate <tt>hbm.xml</tt> file or through a {@link javax.persistence.SqlResultSetMapping} annotation.
+	 *
+	 * @param name The name of the mapping to use.
+	 *
+	 * @return this, for method chaining
 	 */
-	public SQLQuery addEntity(Class entityClass);
+	public SQLQuery setResultSetMapping(String name);
+
 	/**
+	 * Declare a scalar query result. Hibernate will attempt to automatically detect the underlying type.
+	 * <p/>
+	 * Functions like {@code <return-scalar/>} in {@code hbm.xml} or {@link javax.persistence.ColumnResult}
+	 *
+	 * @param columnAlias The column alias in the result-set to be processed as a scalar result
+	 *
+	 * @return {@code this}, for method chaining
+	 */
+	public SQLQuery addScalar(String columnAlias);
+
+	/**
+	 * Declare a scalar query result.
+	 * <p/>
+	 * Functions like {@code <return-scalar/>} in {@code hbm.xml} or {@link javax.persistence.ColumnResult}
+	 *
+	 * @param columnAlias The column alias in the result-set to be processed as a scalar result
+	 * @param type The Hibernate type as which to treat the value.
+	 *
+	 * @return {@code this}, for method chaining
+	 */
+	public SQLQuery addScalar(String columnAlias, Type type);
+
+	/**
+	 * Add a new root return mapping, returning a {@link RootReturn} to allow further definition
+	 *
+	 * @param tableAlias The SQL table alias to map to this entity
+	 * @param entityName The name of the entity.
+	 *
+	 * @return The return config object for further control.
+	 *
+	 * @since 3.6
+	 */
+	public RootReturn addRoot(String tableAlias, String entityName);
+
+	/**
+	 * Add a new root return mapping, returning a {@link RootReturn} to allow further definition
+	 *
+	 * @param tableAlias The SQL table alias to map to this entity
+	 * @param entityType The java type of the entity.
+	 *
+	 * @return The return config object for further control.
+	 *
+	 * @since 3.6
+	 */
+	public RootReturn addRoot(String tableAlias, Class entityType);
+
+	/**
+	 * Declare a "root" entity, without specifying an alias.  The expectation here is that the table alias is the
+	 * same as the unqualified entity name
+	 * <p/>
+	 * Use {@link #addRoot} if you need further control of the mapping
+	 *
+	 * @param entityName The entity name that is the root return of the query.
+	 *
+	 * @return {@code this}, for method chaining
+	 */
+	public SQLQuery addEntity(String entityName);
+
+	/**
 	 * Declare a "root" entity
+	 *
+	 * @param tableAlias The SQL table alias
+	 * @param entityName The entity name
+	 *
+	 * @return {@code this}, for method chaining
 	 */
-	public SQLQuery addEntity(String alias, Class entityClass);
+	public SQLQuery addEntity(String tableAlias, String entityName);
+
 	/**
 	 * Declare a "root" entity, specifying a lock mode
+	 *
+	 * @param tableAlias The SQL table alias
+	 * @param entityName The entity name
+	 * @param lockMode The lock mode for this return.
+	 *
+	 * @return {@code this}, for method chaining
 	 */
-	public SQLQuery addEntity(String alias, Class entityClass, LockMode lockMode);
+	public SQLQuery addEntity(String tableAlias, String entityName, LockMode lockMode);
 
 	/**
-	 * Declare a "joined" entity
+	 * Declare a "root" entity, without specifying an alias.  The expectation here is that the table alias is the
+	 * same as the unqualified entity name
+	 *
+	 * @param entityType The java type of the entity to add as a root
+	 *
+	 * @return {@code this}, for method chaining
 	 */
-	public SQLQuery addJoin(String alias, String path);
+	public SQLQuery addEntity(Class entityType);
+
 	/**
-	 * Declare a "joined" entity, specifying a lock mode
+	 * Declare a "root" entity
+	 *
+	 * @param tableAlias The SQL table alias
+	 * @param entityType The java type of the entity to add as a root
+	 *
+	 * @return {@code this}, for method chaining
 	 */
-	public SQLQuery addJoin(String alias, String path, LockMode lockMode);
-	
+	public SQLQuery addEntity(String tableAlias, Class entityType);
+
 	/**
-	 * Declare a scalar query result
+	 * Declare a "root" entity, specifying a lock mode
+	 *
+	 * @param tableAlias The SQL table alias
+	 * @param entityName The entity name
+	 * @param lockMode The lock mode for this return.
+	 *
+	 * @return {@code this}, for method chaining
 	 */
-	public SQLQuery addScalar(String columnAlias, Type type);
+	public SQLQuery addEntity(String tableAlias, Class entityName, LockMode lockMode);
 
 	/**
-	 * Declare a scalar query. Hibernate will attempt to automatically detect the underlying type.
+	 * Declare a join fetch result.
+	 *
+	 * @param tableAlias The SQL table alias for the data to be mapped to this fetch
+	 * @param ownerTableAlias Identify the table alias of the owner of this association.  Should match the alias of a
+	 * previously added root or fetch
+	 * @param joinPropertyName The name of the property being join fetched.
+	 *
+	 * @return The return config object for further control.
+	 *
+	 * @since 3.6
 	 */
-	public SQLQuery addScalar(String columnAlias);
+	public FetchReturn addFetch(String tableAlias, String ownerTableAlias, String joinPropertyName);
 
 	/**
-	 * Use a predefined named ResultSetMapping
+	 * Declare a join fetch result.
+	 *
+	 * @param tableAlias The SQL table alias for the data to be mapped to this fetch
+	 * @param path The association path ([owner-alias].[property-name]).
+	 *
+	 * @return {@code this}, for method chaining
 	 */
-	public SQLQuery setResultSetMapping(String name);
+	public SQLQuery addJoin(String tableAlias, String path);
 
 	/**
-	 * Adds a query space for auto-flush synchronization.
+	 * Declare a join fetch result.
 	 *
-	 * @param querySpace The query space to be auto-flushed for this query.
-	 * @return this, for method chaning
+	 * @param tableAlias The SQL table alias for the data to be mapped to this fetch
+	 * @param ownerTableAlias Identify the table alias of the owner of this association.  Should match the alias of a
+	 * previously added root or fetch
+	 * @param joinPropertyName The name of the property being join fetched.
+	 *
+	 * @return {@code this}, for method chaining
+	 *
+	 * @since 3.6
 	 */
-	public SQLQuery addSynchronizedQuerySpace(String querySpace);
+	public SQLQuery addJoin(String tableAlias, String ownerTableAlias, String joinPropertyName);
 
 	/**
-	 * Adds an entity name or auto-flush synchronization.
+	 * Declare a join fetch result, specifying a lock mode
 	 *
-	 * @param entityName The name of the entity upon whose defined
-	 * query spaces we should additionally synchronize.
-	 * @return this, for method chaning
-	 * @throws MappingException Indicates the given entity name could not be
-	 * resolved.
+	 * @param tableAlias The SQL table alias for the data to be mapped to this fetch
+	 * @param path The association path ([owner-alias].[property-name]).
+	 * @param lockMode The lock mode for this return.
+	 *
+	 * @return {@code this}, for method chaining
 	 */
-	public SQLQuery addSynchronizedEntityName(String entityName) throws MappingException;
+	public SQLQuery addJoin(String tableAlias, String path, LockMode lockMode);
 
 	/**
-	 * Adds an entity name or auto-flush synchronization.
-	 *
-	 * @param entityClass The class of the entity upon whose defined
-	 * query spaces we should additionally synchronize.
-	 * @return this, for method chaning
-	 * @throws MappingException Indicates the given entity class could not be
-	 * resolved.
+	 * Allows access to further control how properties within a root or join fetch are mapped back from the result set.
+	 * Generally used in composite value scenarios.
 	 */
-	public SQLQuery addSynchronizedEntityClass(Class entityClass) throws MappingException;
+	public static interface ReturnProperty {
+		/**
+		 * Add a column alias to this property mapping.
+		 *
+		 * @param columnAlias The column alias.
+		 *
+		 * @return {@code this}, for method chaining
+		 */
+		public ReturnProperty addColumnAlias(String columnAlias);
+	}
+
+	/**
+	 * Allows access to further control how root returns are mapped back from result sets
+	 */
+	public static interface RootReturn {
+		/**
+		 * Set the lock mode for this return
+		 *
+		 * @param lockMode The new lock mode.
+		 *
+		 * @return {@code this}, for method chaining
+		 */
+		public RootReturn setLockMode(LockMode lockMode);
+
+		/**
+		 * Name the column alias that identifies the entity's discriminator
+		 *
+		 * @param columnAlias The discriminator column alias
+		 *
+		 * @return {@code this}, for method chaining
+		 */
+		public RootReturn setDiscriminatorAlias(String columnAlias);
+
+		/**
+		 * Add a simple property-to-one-column mapping
+		 *
+		 * @param propertyName The name of the property.
+		 * @param columnAlias The name of the column
+		 *
+		 * @return {@code this}, for method chaining
+		 */
+		public RootReturn addProperty(String propertyName, String columnAlias);
+
+		/**
+		 * Add a property, presumably with more than one column.
+		 *
+		 * @param propertyName The name of the property.
+		 *
+		 * @return The config object for further control.
+		 */
+		public ReturnProperty addProperty(String propertyName);
+	}
+
+	/**
+	 * Allows access to further control how join fetch returns are mapped back from result sets
+	 */
+	public static interface FetchReturn {
+		/**
+		 * Set the lock mode for this return
+		 *
+		 * @param lockMode The new lock mode.
+		 *
+		 * @return {@code this}, for method chaining
+		 */
+		public FetchReturn setLockMode(LockMode lockMode);
+
+		/**
+		 * Add a simple property-to-one-column mapping
+		 *
+		 * @param propertyName The name of the property.
+		 * @param columnAlias The name of the column
+		 *
+		 * @return {@code this}, for method chaining
+		 */
+		public FetchReturn addProperty(String propertyName, String columnAlias);
+
+		/**
+		 * Add a property, presumably with more than one column.
+		 *
+		 * @param propertyName The name of the property.
+		 *
+		 * @return The config object for further control.
+		 */
+		public ReturnProperty addProperty(String propertyName);
+	}
 }

Added: core/trunk/core/src/main/java/org/hibernate/SQLQueryResultMappingBuilder.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/SQLQueryResultMappingBuilder.java	                        (rev 0)
+++ core/trunk/core/src/main/java/org/hibernate/SQLQueryResultMappingBuilder.java	2010-07-22 20:23:08 UTC (rev 20014)
@@ -0,0 +1,53 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
+ * indicated by the @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;
+
+import org.hibernate.engine.query.sql.NativeSQLQueryReturn;
+import org.hibernate.type.Type;
+
+/**
+ * Allows programmatic access from {@link SQLQuery} to define how to map SQL {@link java.sql.ResultSet results}
+ * back to in-memory objects, both entities as well as scalars.  Essentially it defines an API akin to the
+ * {@code <return/>}, {@code <return-scalar/>} and {@code <return-join/>} elements under {@code <sql-query/>}
+ * definition in a Hibernate <tt>hbm.xml</tt> file.
+ *
+ * @author Steve Ebersole
+ */
+public interface SQLQueryResultMappingBuilder {
+
+	public static interface ReturnsHolder {
+		public void add(NativeSQLQueryReturn queryReturn);
+	}
+
+	public static class ScalarReturn {
+		private final ReturnsHolder returnsHolder;
+		private String name;
+		private Type type;
+
+		public ScalarReturn(ReturnsHolder returnsHolder) {
+			this.returnsHolder = returnsHolder;
+		}
+	}
+
+}

Modified: core/trunk/core/src/main/java/org/hibernate/cfg/NamedSQLQuerySecondPass.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/cfg/NamedSQLQuerySecondPass.java	2010-07-22 19:52:43 UTC (rev 20013)
+++ core/trunk/core/src/main/java/org/hibernate/cfg/NamedSQLQuerySecondPass.java	2010-07-22 20:23:08 UTC (rev 20014)
@@ -68,7 +68,7 @@
 		Attribute cmAtt = queryElem.attribute( "comment" );
 		String comment = cmAtt == null ? null : cmAtt.getValue();
 
-		java.util.List synchronizedTables = new ArrayList();
+		java.util.List<String> synchronizedTables = new ArrayList<String>();
 		Iterator tables = queryElem.elementIterator( "synchronize" );
 		while ( tables.hasNext() ) {
 			synchronizedTables.add( ( (Element) tables.next() ).attributeValue( "table" ) );

Modified: core/trunk/core/src/main/java/org/hibernate/classic/Session.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/classic/Session.java	2010-07-22 19:52:43 UTC (rev 20013)
+++ core/trunk/core/src/main/java/org/hibernate/classic/Session.java	2010-07-22 20:23:08 UTC (rev 20014)
@@ -31,6 +31,7 @@
 
 import org.hibernate.HibernateException;
 import org.hibernate.Query;
+import org.hibernate.SQLQuery;
 import org.hibernate.type.Type;
 
 /**
@@ -297,24 +298,34 @@
 
 	/**
 	 * Create a new instance of <tt>Query</tt> for the given SQL string.
+	 * <p/>
+	 * Returned instances should all be {@link org.hibernate.SQLQuery}.
 	 *
-	 * @deprecated will be replaced with a more Query like interface in later release
-	 *
 	 * @param sql a query expressed in SQL
 	 * @param returnAlias a table alias that appears inside <tt>{}</tt> in the SQL string
 	 * @param returnClass the returned persistent class
+	 *
+	 * @deprecated use {@link org.hibernate.SQLQuery#addRoot} or {@link org.hibernate.SQLQuery#addEntity} variants
+	 * instead to define the alias/class
 	 */
+	@Deprecated
+	@SuppressWarnings({ "JavaDoc" })
 	public Query createSQLQuery(String sql, String returnAlias, Class returnClass);
 	
 	/**
 	 * Create a new instance of <tt>Query</tt> for the given SQL string.
+	 * <p/>
+	 * Returned instances should all be {@link org.hibernate.SQLQuery}.
 	 *
-	 * @deprecated will be replaced with a more Query like interface in later release
-	 *
 	 * @param sql a query expressed in SQL
 	 * @param returnAliases an array of table aliases that appear inside <tt>{}</tt> in the SQL string
 	 * @param returnClasses the returned persistent classes
+	 *
+	 * @deprecated use {@link org.hibernate.SQLQuery#addRoot} or {@link org.hibernate.SQLQuery#addEntity} variants
+	 * instead to define the aliases/classes
 	 */
+	@Deprecated
+	@SuppressWarnings({ "JavaDoc" })
 	public Query createSQLQuery(String sql, String[] returnAliases, Class[] returnClasses);
 	
 	

Modified: core/trunk/core/src/main/java/org/hibernate/engine/NamedSQLQueryDefinition.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/engine/NamedSQLQueryDefinition.java	2010-07-22 19:52:43 UTC (rev 20013)
+++ core/trunk/core/src/main/java/org/hibernate/engine/NamedSQLQueryDefinition.java	2010-07-22 20:23:08 UTC (rev 20014)
@@ -1,10 +1,10 @@
 /*
  * Hibernate, Relational Persistence for Idiomatic Java
  *
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
  * indicated by the @author tags or express copyright attribution
  * statements applied by the authors.  All third-party contributions are
- * distributed under license by Red Hat Middleware LLC.
+ * 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
@@ -20,7 +20,6 @@
  * Free Software Foundation, Inc.
  * 51 Franklin Street, Fifth Floor
  * Boston, MA  02110-1301  USA
- *
  */
 package org.hibernate.engine;
 
@@ -32,15 +31,14 @@
 import org.hibernate.engine.query.sql.NativeSQLQueryReturn;
 
 /**
- * Definition of a named native SQL query, defined
- * in the mapping metadata.
+ * Definition of a named native SQL query, defined in the mapping metadata.
  * 
  * @author Max Andersen
  */
 public class NamedSQLQueryDefinition extends NamedQueryDefinition {
 
 	private NativeSQLQueryReturn[] queryReturns;
-	private final List querySpaces;
+	private final List<String> querySpaces;
 	private final boolean callable;
 	private String resultSetRef;
 
@@ -66,7 +64,7 @@
 	public NamedSQLQueryDefinition(
 			String query,
 			NativeSQLQueryReturn[] queryReturns,
-			List querySpaces,
+			List<String> querySpaces,
 			boolean cacheable,
 			String cacheRegion,
 			Integer timeout,
@@ -115,7 +113,7 @@
 	public NamedSQLQueryDefinition(
 			String query,
 			String resultSetRef,
-			List querySpaces,
+			List<String> querySpaces,
 			boolean cacheable,
 			String cacheRegion,
 			Integer timeout,
@@ -165,7 +163,7 @@
 	public NamedSQLQueryDefinition(
 			String query,
 			String resultSetRef,
-			List querySpaces,
+			List<String> querySpaces,
 			boolean cacheable,
 			String cacheRegion,
 			Integer timeout,
@@ -194,7 +192,7 @@
 		return queryReturns;
 	}
 
-	public List getQuerySpaces() {
+	public List<String> getQuerySpaces() {
 		return querySpaces;
 	}
 

Modified: core/trunk/core/src/main/java/org/hibernate/engine/query/sql/NativeSQLQueryReturn.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/engine/query/sql/NativeSQLQueryReturn.java	2010-07-22 19:52:43 UTC (rev 20013)
+++ core/trunk/core/src/main/java/org/hibernate/engine/query/sql/NativeSQLQueryReturn.java	2010-07-22 20:23:08 UTC (rev 20014)
@@ -26,6 +26,8 @@
 
 /**
  * Describes a return in a native SQL query.
+ * <p/>
+ * IMPL NOTE : implementations should be immutable as they are used as part of cache keys for result caching.
  *
  * @author Steve Ebersole
  */

Modified: core/trunk/core/src/main/java/org/hibernate/impl/SQLQueryImpl.java
===================================================================
--- core/trunk/core/src/main/java/org/hibernate/impl/SQLQueryImpl.java	2010-07-22 19:52:43 UTC (rev 20013)
+++ core/trunk/core/src/main/java/org/hibernate/impl/SQLQueryImpl.java	2010-07-22 20:23:08 UTC (rev 20014)
@@ -1,10 +1,10 @@
 /*
  * Hibernate, Relational Persistence for Idiomatic Java
  *
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
  * indicated by the @author tags or express copyright attribution
  * statements applied by the authors.  All third-party contributions are
- * distributed under license by Red Hat Middleware LLC.
+ * 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
@@ -20,13 +20,13 @@
  * Free Software Foundation, Inc.
  * 51 Franklin Street, Fifth Floor
  * Boston, MA  02110-1301  USA
- *
  */
 package org.hibernate.impl;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -53,28 +53,23 @@
 import org.hibernate.engine.query.sql.NativeSQLQueryRootReturn;
 import org.hibernate.engine.query.sql.NativeSQLQueryReturn;
 import org.hibernate.type.Type;
-import org.hibernate.util.CollectionHelper;
 import org.hibernate.util.StringHelper;
 
 /**
- * Implements SQL query passthrough.
+ * Implementation of the {@link SQLQuery} contract.
  *
- * <pre>
- * <sql-query name="mySqlQuery">
- * <return alias="person" class="eg.Person"/>
- *   SELECT {person}.NAME AS {person.name}, {person}.AGE AS {person.age}, {person}.SEX AS {person.sex}
- *   FROM PERSON {person} WHERE {person}.NAME LIKE 'Hiber%'
- * </sql-query>
- * </pre>
- *
  * @author Max Andersen
+ * @author Steve Ebersole
  */
 public class SQLQueryImpl extends AbstractQueryImpl implements SQLQuery {
 
-	private final List queryReturns;
-	private Collection querySpaces;
+	private List<NativeSQLQueryReturn> queryReturns;
+	private List<ReturnBuilder> queryReturnBuilders;
+	private boolean autoDiscoverTypes;
+
+	private Collection<String> querySpaces;
+
 	private final boolean callable;
-	private boolean autodiscovertypes;
 
 	/**
 	 * Constructs a SQLQueryImpl given a sql query defined in the mappings.
@@ -100,7 +95,7 @@
 			this.queryReturns = Arrays.asList( queryDef.getQueryReturns() );
 		}
 		else {
-			this.queryReturns = new ArrayList();
+			this.queryReturns = new ArrayList<NativeSQLQueryReturn>();
 		}
 
 		this.querySpaces = queryDef.getQuerySpaces();
@@ -109,31 +104,16 @@
 
 	SQLQueryImpl(
 			final String sql,
-	        final List queryReturns,
-	        final Collection querySpaces,
-	        final FlushMode flushMode,
-	        boolean callable,
-	        final SessionImplementor session,
-	        ParameterMetadata parameterMetadata) {
-		// TODO : absolutely no usages of this constructor form; can it go away?
-		super( sql, flushMode, session, parameterMetadata );
-		this.queryReturns = queryReturns;
-		this.querySpaces = querySpaces;
-		this.callable = callable;
-	}
-
-	SQLQueryImpl(
-			final String sql,
 	        final String returnAliases[],
 	        final Class returnClasses[],
 	        final LockMode[] lockModes,
 	        final SessionImplementor session,
-	        final Collection querySpaces,
+	        final Collection<String> querySpaces,
 	        final FlushMode flushMode,
 	        ParameterMetadata parameterMetadata) {
 		// TODO : this constructor form is *only* used from constructor directly below us; can it go away?
 		super( sql, flushMode, session, parameterMetadata );
-		queryReturns = new ArrayList(returnAliases.length);
+		queryReturns = new ArrayList<NativeSQLQueryReturn>( returnAliases.length );
 		for ( int i=0; i<returnAliases.length; i++ ) {
 			NativeSQLQueryRootReturn ret = new NativeSQLQueryRootReturn(
 					returnAliases[i],
@@ -157,15 +137,13 @@
 
 	SQLQueryImpl(String sql, SessionImplementor session, ParameterMetadata parameterMetadata) {
 		super( sql, null, session, parameterMetadata );
-		queryReturns = new ArrayList();
+		queryReturns = new ArrayList<NativeSQLQueryReturn>();
 		querySpaces = null;
 		callable = false;
 	}
 
-	private static final NativeSQLQueryReturn[] NO_SQL_RETURNS = new NativeSQLQueryReturn[0];
-
 	private NativeSQLQueryReturn[] getQueryReturns() {
-		return ( NativeSQLQueryReturn[] ) queryReturns.toArray( NO_SQL_RETURNS );
+		return queryReturns.toArray( new NativeSQLQueryReturn[queryReturns.size()] );
 	}
 
 	public List list() throws HibernateException {
@@ -220,24 +198,25 @@
 	public QueryParameters getQueryParameters(Map namedParams) {
 		QueryParameters qp = super.getQueryParameters(namedParams);
 		qp.setCallable(callable);
-		qp.setAutoDiscoverScalarTypes(autodiscovertypes);
+		qp.setAutoDiscoverScalarTypes( autoDiscoverTypes );
 		return qp;
 	}
 
 	protected void verifyParameters() {
+		// verifyParameters is called at the start of all execution type methods, so we use that here to perform
+		// some preparation work.
+		prepare();
 		verifyParameters( callable );
 		boolean noReturns = queryReturns==null || queryReturns.isEmpty();
 		if ( noReturns ) {
-			this.autodiscovertypes = noReturns;
+			this.autoDiscoverTypes = noReturns;
 		}
 		else {
-			Iterator itr = queryReturns.iterator();
-			while ( itr.hasNext() ) {
-				NativeSQLQueryReturn rtn = ( NativeSQLQueryReturn ) itr.next();
-				if ( rtn instanceof NativeSQLQueryScalarReturn ) {
-					NativeSQLQueryScalarReturn scalar = ( NativeSQLQueryScalarReturn ) rtn;
+			for ( NativeSQLQueryReturn queryReturn : queryReturns ) {
+				if ( queryReturn instanceof NativeSQLQueryScalarReturn ) {
+					NativeSQLQueryScalarReturn scalar = (NativeSQLQueryScalarReturn) queryReturn;
 					if ( scalar.getType() == null ) {
-						autodiscovertypes = true;
+						autoDiscoverTypes = true;
 						break;
 					}
 				}
@@ -245,6 +224,23 @@
 		}
 	}
 
+	private void prepare() {
+		if ( queryReturnBuilders != null ) {
+			if ( ! queryReturnBuilders.isEmpty() ) {
+				if ( queryReturns != null ) {
+					queryReturns.clear();
+					queryReturns = null;
+				}
+				queryReturns = new ArrayList<NativeSQLQueryReturn>();
+				for ( ReturnBuilder builder : queryReturnBuilders ) {
+					queryReturns.add( builder.buildReturn() );
+				}
+				queryReturnBuilders.clear();
+			}
+			queryReturnBuilders = null;
+		}
+	}
+
 	public String[] getReturnAliases() throws HibernateException {
 		throw new UnsupportedOperationException("SQL queries do not currently support returning aliases");
 	}
@@ -266,23 +262,35 @@
 		return null;
 	}
 
-	public SQLQuery addScalar(String columnAlias, Type type) {
-		queryReturns.add( new NativeSQLQueryScalarReturn( columnAlias, type ) );
+	public SQLQuery addScalar(final String columnAlias, final Type type) {
+		if ( queryReturnBuilders == null ) {
+			queryReturnBuilders = new ArrayList<ReturnBuilder>();
+		}
+		queryReturnBuilders.add(
+				new ReturnBuilder() {
+					public NativeSQLQueryReturn buildReturn() {
+						return new NativeSQLQueryScalarReturn( columnAlias, type );
+					}
+				}
+		);
 		return this;
 	}
 
 	public SQLQuery addScalar(String columnAlias) {
-		autodiscovertypes = true;
-		queryReturns.add( new NativeSQLQueryScalarReturn( columnAlias, null ) );
-		return this;
+		return addScalar( columnAlias, null );
 	}
 
-	public SQLQuery addJoin(String alias, String path) {
-		return addJoin(alias, path, LockMode.READ);
+	public RootReturn addRoot(String tableAlias, String entityName) {
+		RootReturnBuilder builder = new RootReturnBuilder( tableAlias, entityName );
+		if ( queryReturnBuilders == null ) {
+			queryReturnBuilders = new ArrayList<ReturnBuilder>();
+		}
+		queryReturnBuilders.add( builder );
+		return builder;
 	}
 
-	public SQLQuery addEntity(Class entityClass) {
-		return addEntity( StringHelper.unqualify( entityClass.getName() ), entityClass );
+	public RootReturn addRoot(String tableAlias, Class entityType) {
+		return addRoot( tableAlias, entityType.getName() );
 	}
 
 	public SQLQuery addEntity(String entityName) {
@@ -290,49 +298,74 @@
 	}
 
 	public SQLQuery addEntity(String alias, String entityName) {
-		return addEntity(alias, entityName, LockMode.READ);
+		addRoot( alias, entityName );
+		return this;
 	}
 
+	public SQLQuery addEntity(String alias, String entityName, LockMode lockMode) {
+		addRoot( alias, entityName ).setLockMode( lockMode );
+		return this;
+	}
+
+	public SQLQuery addEntity(Class entityType) {
+		return addEntity( entityType.getName() );
+	}
+
 	public SQLQuery addEntity(String alias, Class entityClass) {
 		return addEntity( alias, entityClass.getName() );
 	}
 
-	public SQLQuery addJoin(String alias, String path, LockMode lockMode) {
+	public SQLQuery addEntity(String alias, Class entityClass, LockMode lockMode) {
+		return addEntity( alias, entityClass.getName(), lockMode );
+	}
+
+	public FetchReturn addFetch(String tableAlias, String ownerTableAlias, String joinPropertyName) {
+		FetchReturnBuilder builder = new FetchReturnBuilder( tableAlias, ownerTableAlias, joinPropertyName );
+		if ( queryReturnBuilders == null ) {
+			queryReturnBuilders = new ArrayList<ReturnBuilder>();
+		}
+		queryReturnBuilders.add( builder );
+		return builder;
+	}
+
+	public SQLQuery addJoin(String tableAlias, String ownerTableAlias, String joinPropertyName) {
+		addFetch( tableAlias, ownerTableAlias, joinPropertyName );
+		return this;
+	}
+
+	public SQLQuery addJoin(String alias, String path) {
+		createFetchJoin( alias, path );
+		return this;
+	}
+
+	private FetchReturn createFetchJoin(String tableAlias, String path) {
 		int loc = path.indexOf('.');
 		if ( loc < 0 ) {
 			throw new QueryException( "not a property path: " + path );
 		}
-		String ownerAlias = path.substring(0, loc);
-		String role = path.substring(loc+1);
-		queryReturns.add( new NativeSQLQueryJoinReturn(alias, ownerAlias, role, CollectionHelper.EMPTY_MAP, lockMode) );
-		return this;
+		final String ownerTableAlias = path.substring( 0, loc );
+		final String joinedPropertyName = path.substring( loc+1 );
+		return addFetch( tableAlias, ownerTableAlias, joinedPropertyName );
 	}
 
-	public SQLQuery addEntity(String alias, String entityName, LockMode lockMode) {
-		queryReturns.add( new NativeSQLQueryRootReturn(alias, entityName, lockMode) );
+	public SQLQuery addJoin(String alias, String path, LockMode lockMode) {
+		createFetchJoin( alias, path ).setLockMode( lockMode );
 		return this;
 	}
 
-	public SQLQuery addEntity(String alias, Class entityClass, LockMode lockMode) {
-		return addEntity( alias, entityClass.getName(), lockMode );
-	}
-
 	public SQLQuery setResultSetMapping(String name) {
 		ResultSetMappingDefinition mapping = session.getFactory().getResultSetMapping( name );
 		if ( mapping == null ) {
 			throw new MappingException( "Unknown SqlResultSetMapping [" + name + "]" );
 		}
 		NativeSQLQueryReturn[] returns = mapping.getQueryReturns();
-		int length = returns.length;
-		for ( int index = 0 ; index < length ; index++ ) {
-			queryReturns.add( returns[index] );
-		}
+		queryReturns.addAll( Arrays.asList( returns ) );
 		return this;
 	}
 
 	public SQLQuery addSynchronizedQuerySpace(String querySpace) {
 		if ( querySpaces == null ) {
-			querySpaces = new ArrayList();
+			querySpaces = new ArrayList<String>();
 		}
 		querySpaces.add( querySpace );
 		return this;
@@ -349,11 +382,9 @@
 	private SQLQuery addQuerySpaces(Serializable[] spaces) {
 		if ( spaces != null ) {
 			if ( querySpaces == null ) {
-				querySpaces = new ArrayList();
+				querySpaces = new ArrayList<String>();
 			}
-			for ( int i = 0; i < spaces.length; i++ ) {
-				querySpaces.add( spaces[i] );
-			}
+			querySpaces.addAll( Arrays.asList( (String[]) spaces ) );
 		}
 		return this;
 	}
@@ -372,4 +403,97 @@
 		}
 	}
 
+	private class RootReturnBuilder implements RootReturn, ReturnBuilder {
+		private final String alias;
+		private final String entityName;
+		private LockMode lockMode = LockMode.READ;
+		private Map<String,List<String>> propertyMappings;
+
+		private RootReturnBuilder(String alias, String entityName) {
+			this.alias = alias;
+			this.entityName = entityName;
+		}
+
+		public RootReturn setLockMode(LockMode lockMode) {
+			this.lockMode = lockMode;
+			return this;
+		}
+
+		public RootReturn setDiscriminatorAlias(String alias) {
+			addProperty( "class", alias );
+			return this;
+		}
+
+		public RootReturn addProperty(String propertyName, String columnAlias) {
+			addProperty( propertyName ).addColumnAlias( columnAlias );
+			return this;
+		}
+
+		public ReturnProperty addProperty(final String propertyName) {
+			if ( propertyMappings == null ) {
+				propertyMappings = new HashMap<String,List<String>>();
+			}
+			return new ReturnProperty() {
+				public ReturnProperty addColumnAlias(String columnAlias) {
+					List<String> columnAliases = propertyMappings.get( propertyName );
+					if ( columnAliases == null ) {
+						columnAliases = new ArrayList<String>();
+					}
+					columnAliases.add( columnAlias );
+					return this;
+				}
+			};
+		}
+
+		public NativeSQLQueryReturn buildReturn() {
+			return new NativeSQLQueryRootReturn( alias, entityName, propertyMappings, lockMode );
+		}
+	}
+	private class FetchReturnBuilder implements FetchReturn, ReturnBuilder {
+		private final String alias;
+		private String ownerTableAlias;
+		private final String joinedPropertyName;
+		private LockMode lockMode = LockMode.READ;
+		private Map<String,List<String>> propertyMappings;
+
+		private FetchReturnBuilder(String alias, String ownerTableAlias, String joinedPropertyName) {
+			this.alias = alias;
+			this.ownerTableAlias = ownerTableAlias;
+			this.joinedPropertyName = joinedPropertyName;
+		}
+
+		public FetchReturn setLockMode(LockMode lockMode) {
+			this.lockMode = lockMode;
+			return this;
+		}
+
+		public FetchReturn addProperty(String propertyName, String columnAlias) {
+			addProperty( propertyName ).addColumnAlias( columnAlias );
+			return this;
+		}
+
+		public ReturnProperty addProperty(final String propertyName) {
+			if ( propertyMappings == null ) {
+				propertyMappings = new HashMap<String,List<String>>();
+			}
+			return new ReturnProperty() {
+				public ReturnProperty addColumnAlias(String columnAlias) {
+					List<String> columnAliases = propertyMappings.get( propertyName );
+					if ( columnAliases == null ) {
+						columnAliases = new ArrayList<String>();
+					}
+					columnAliases.add( columnAlias );
+					return this;
+				}
+			};
+		}
+
+		public NativeSQLQueryReturn buildReturn() {
+			return new NativeSQLQueryJoinReturn( alias, ownerTableAlias, joinedPropertyName, propertyMappings, lockMode );
+		}
+	}
+
+	private interface ReturnBuilder {
+		NativeSQLQueryReturn buildReturn();
+	}
 }

Modified: core/trunk/testsuite/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java
===================================================================
--- core/trunk/testsuite/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java	2010-07-22 19:52:43 UTC (rev 20013)
+++ core/trunk/testsuite/src/test/java/org/hibernate/test/sql/hand/query/NativeSQLQueriesTest.java	2010-07-22 20:23:08 UTC (rev 20014)
@@ -13,9 +13,14 @@
 import org.hibernate.Hibernate;
 import org.hibernate.HibernateException;
 import org.hibernate.Query;
+import org.hibernate.SQLQuery;
 import org.hibernate.Session;
 import org.hibernate.Transaction;
 import org.hibernate.dialect.H2Dialect;
+import org.hibernate.type.FloatType;
+import org.hibernate.type.LongType;
+import org.hibernate.type.StringType;
+import org.hibernate.type.TimestampType;
 import org.hibernate.util.ArrayHelper;
 import org.hibernate.test.sql.hand.Organization;
 import org.hibernate.test.sql.hand.Person;
@@ -41,6 +46,7 @@
  *
  * @author Steve Ebersole
  */
+ at SuppressWarnings({ "UnnecessaryBoxing", "UnnecessaryUnboxing" })
 public class NativeSQLQueriesTest extends FunctionalTestCase {
 
 	public NativeSQLQueriesTest(String x) {
@@ -606,6 +612,87 @@
 
 	}
 
+	public void testExplicitReturnAPI() {
+		Session s = openSession();
+		s.beginTransaction();
+		Organization jboss = new Organization( "JBoss" );
+		Person me = new Person( "Steve" );
+		Employment emp = new Employment( me, jboss, "US" );
+		Serializable jbossId = s.save( jboss );
+		s.save( me );
+		s.save( emp );
+		s.getTransaction().commit();
+		s.close();
+
+		s = openSession();
+		s.beginTransaction();
+
+		String sql =
+				"SELECT org.ORGID 		as orgid," +
+				"       org.NAME 		as name," +
+				"       emp.EMPLOYER 	as employer," +
+				"       emp.EMPID 		as empid," +
+				"       emp.EMPLOYEE 	as employee," +
+				"       emp.EMPLOYER 	as employer," +
+				"       emp.STARTDATE 	as startDate," +
+				"       emp.ENDDATE 	as endDate," +
+				"       emp.REGIONCODE 	as regionCode," +
+				"       emp.VALUE 		as VALUE," +
+				"       emp.CURRENCY 	as CURRENCY" +
+				" FROM 	ORGANIZATION org" +
+				"    LEFT OUTER JOIN EMPLOYMENT emp ON org.ORGID = emp.EMPLOYER";
+
+		// as a control, lets apply an existing rs mapping
+		SQLQuery sqlQuery = s.createSQLQuery( sql );
+		sqlQuery.setResultSetMapping( "org-description" );
+		sqlQuery.list();
+
+		// next try a partial mapping def
+		sqlQuery.addRoot( "org", Organization.class );
+		sqlQuery.addFetch( "emp", "org", "employments" );
+		sqlQuery.list();
+
+		// now try full explicit mappings
+		sqlQuery.addRoot( "org", Organization.class )
+				.addProperty( "id", "orgid" )
+				.addProperty( "name" ).addColumnAlias( "name" );
+		sqlQuery.addFetch( "emp", "org", "employments" )
+				.addProperty( "key", "employer" )
+				.addProperty( "element", "empid" )
+				.addProperty( "element.employee", "employee" )
+				.addProperty( "element.employer", "employer" )
+				.addProperty( "element.startDate", "startDate" )
+				.addProperty( "element.endDate", "endDate" )
+				.addProperty( "element.regionCode", "regionCode" )
+				.addProperty( "element.employmentId", "empId" )
+				.addProperty( "element.salary" ).addColumnAlias( "VALUE" ).addColumnAlias( "CURRENCY" );
+		sqlQuery.list();
+
+		// lets try a totally different approach now and pull back scalars, first with explicit types
+		sqlQuery.addScalar( "orgid", LongType.INSTANCE )
+				.addScalar( "name", StringType.INSTANCE )
+				.addScalar( "empid", LongType.INSTANCE )
+				.addScalar( "employee", LongType.INSTANCE )
+				.addScalar( "startDate", TimestampType.INSTANCE )
+				.addScalar( "endDate", TimestampType.INSTANCE )
+				.addScalar( "regionCode", StringType.INSTANCE )
+				.addScalar( "empId", LongType.INSTANCE )
+				.addScalar( "VALUE", FloatType.INSTANCE )
+				.addScalar( "CURRENCY", StringType.INSTANCE );
+
+
+		s.getTransaction().commit();
+		s.close();
+
+		s = openSession();
+		s.beginTransaction();
+		s.delete( emp );
+		s.delete( jboss );
+		s.delete( me );
+		s.getTransaction().commit();
+		s.close();
+	}
+
 	public void testMixAndMatchEntityScalar() {
 		Session s = openSession();
 		Transaction t = s.beginTransaction();



More information about the hibernate-commits mailing list