diff --git a/hibernate-core/src/main/java/org/hibernate/BasicQueryContract.java b/hibernate-core/src/main/java/org/hibernate/BasicQueryContract.java new file mode 100644 index 0000000000..524299c4ac --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/BasicQueryContract.java @@ -0,0 +1,215 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.type.Type; + +/** + * Defines the aspects of query definition that apply to all forms of querying. + * + * @author Steve Ebersole + */ +public interface BasicQueryContract { + /** + * Obtain the FlushMode in effect for this query. By default, the query inherits the FlushMode of the Session + * from which is originates. + * + * @return The query FlushMode. + * + * @see Session#getFlushMode() + * @see FlushMode + */ + public FlushMode getFlushMode(); + + /** + * (Re)set the current FlushMode in effect for this query. + * + * @param flushMode The new FlushMode to use. + * + * @see #getFlushMode() + */ + public BasicQueryContract setFlushMode(FlushMode flushMode); + + /** + * Obtain the CacheMode in effect for this query. By default, the query inherits the CacheMode of the Session + * from which is originates. + * + * NOTE: The CacheMode here only effects reading/writing of the query cache, not the + * entity/collection caches. + * + * @return The query CacheMode. + * + * @see Session#getCacheMode() + * @see CacheMode + */ + public CacheMode getCacheMode(); + + /** + * (Re)set the current CacheMode in effect for this query. + * + * @param cacheMode The new CacheMode to use. + * + * @see #getCacheMode() + */ + public BasicQueryContract setCacheMode(CacheMode cacheMode); + + /** + * Are the results of this query eligible for second level query caching? This is different that second level + * caching of any returned entities and collections. + * + * NOTE: the query being "eligible" for caching does not necessarily mean its results will be cached. Second level + * query caching still has to be enabled on the {@link SessionFactory} for this to happen. Usually that is + * controlled by the {@code hibernate.cache.use_query_cache} configuration setting. + * + * @return {@code true} if the query results are eligible for caching, {@code false} otherwise. + * + * @see org.hibernate.cfg.AvailableSettings#USE_QUERY_CACHE + */ + public boolean isCacheable(); + + /** + * Enable/disable second level query (result) caching for this query. + * + * @param cacheable Should the query results be cacheable? + * + * @see #isCacheable + */ + public BasicQueryContract setCacheable(boolean cacheable); + + /** + * Obtain the name of the second level query cache region in which query results will be stored (if they are + * cached, see the discussion on {@link #isCacheable()} for more information). {@code null} indicates that the + * default region should be used. + * + * @return The specified cache region name into which query results should be placed; {@code null} indicates + * the default region. + */ + public String getCacheRegion(); + + /** + * Set the name of the cache region where query results should be cached (if cached at all). + * + * @param cacheRegion the name of a query cache region, or {@code null} to indicate that the default region + * should be used. + * + * @see #getCacheRegion() + */ + public BasicQueryContract setCacheRegion(String cacheRegion); + + /** + * Obtain the query timeout in seconds. This value is eventually passed along to the JDBC query via + * {@link java.sql.Statement#setQueryTimeout(int)}. Zero indicates no timeout. + * + * @return The timeout in seconds + * + * @see java.sql.Statement#getQueryTimeout() + * @see java.sql.Statement#setQueryTimeout(int) + */ + public Integer getTimeout(); + + /** + * Set the query timeout in seconds. + * + * NOTE it is important to understand that any value set here is eventually passed directly through to the JDBC + * Statement which expressly disallows negative values. So negative values should be avoided as a general rule. + * + * @param timeout the timeout in seconds + * + * @see #getTimeout() + */ + public BasicQueryContract setTimeout(int timeout); + + /** + * Obtain the JDBC fetch size hint in effect for this query. This value is eventually passed along to the JDBC + * query via {@link java.sql.Statement#setFetchSize(int)}. As defined b y JDBC, this value is a hint to the + * driver to indicate how many rows to fetch from the database when more rows are needed. + * + * NOTE : JDBC expressly defines this value as a hint. It may or may not have any effect on the actual + * query execution and ResultSet processing depending on the driver. + * + * @return The timeout in seconds + * + * @see java.sql.Statement#getFetchSize() + * @see java.sql.Statement#setFetchSize(int) + */ + public Integer getFetchSize(); + + /** + * Sets a JDBC fetch size hint for the query. + * + * @param fetchSize the fetch size hint + * + * @see #getFetchSize() + */ + public BasicQueryContract setFetchSize(int fetchSize); + + /** + * Should entities and proxies loaded by this Query be put in read-only mode? If the + * read-only/modifiable setting was not initialized, then the default + * read-only/modifiable setting for the persistence context is returned instead. + * @see Query#setReadOnly(boolean) + * @see org.hibernate.engine.spi.PersistenceContext#isDefaultReadOnly() + * + * The read-only/modifiable setting has no impact on entities/proxies returned by the + * query that existed in the session before the query was executed. + * + * @return true, entities and proxies loaded by the query will be put in read-only mode + * false, entities and proxies loaded by the query will be put in modifiable mode + */ + public boolean isReadOnly(); + + /** + * Set the read-only/modifiable mode for entities and proxies + * loaded by this Query. This setting overrides the default setting + * for the persistence context. + * @see org.hibernate.engine.spi.PersistenceContext#isDefaultReadOnly() + * + * To set the default read-only/modifiable setting used for + * entities and proxies that are loaded into the session: + * @see org.hibernate.engine.spi.PersistenceContext#setDefaultReadOnly(boolean) + * @see org.hibernate.Session#setDefaultReadOnly(boolean) + * + * Read-only entities are not dirty-checked and snapshots of persistent + * state are not maintained. Read-only entities can be modified, but + * changes are not persisted. + * + * When a proxy is initialized, the loaded entity will have the same + * read-only/modifiable setting as the uninitialized + * proxy has, regardless of the session's current setting. + * + * The read-only/modifiable setting has no impact on entities/proxies + * returned by the query that existed in the session before the query was executed. + * + * @param readOnly true, entities and proxies loaded by the query will be put in read-only mode + * false, entities and proxies loaded by the query will be put in modifiable mode + */ + public BasicQueryContract setReadOnly(boolean readOnly); + + /** + * Return the Hibernate types of the query results. + * + * @return an array of types + */ + public Type[] getReturnTypes() throws HibernateException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/Query.java b/hibernate-core/src/main/java/org/hibernate/Query.java index 6b08d63043..c8f202b4f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/Query.java @@ -77,7 +77,7 @@ import org.hibernate.type.Type; * * @author Gavin King */ -public interface Query { +public interface Query extends BasicQueryContract { /** * Get the query string. * @@ -121,139 +121,27 @@ public interface Query { */ public Query setFirstResult(int firstResult); - /** - * Obtain the FlushMode in effect for this query. By default, the query inherits the FlushMode of the Session - * from which is originates. - * - * @return The query FlushMode. - * - * @see Session#getFlushMode() - * @see FlushMode - */ - public FlushMode getFlushMode(); - - /** - * (Re)set the current FlushMode in effect for this query. - * - * @param flushMode The new FlushMode to use. - * - * @see #getFlushMode() - */ + @Override public Query setFlushMode(FlushMode flushMode); - /** - * Obtain the CacheMode in effect for this query. By default, the query inherits the CacheMode of the Session - * from which is originates. - * - * NOTE: The CacheMode here only effects reading/writing of the query cache, not the - * entity/collection caches. - * - * @return The query CacheMode. - * - * @see Session#getCacheMode() - * @see CacheMode - */ - public CacheMode getCacheMode(); - - /** - * (Re)set the current CacheMode in effect for this query. - * - * @param cacheMode The new CacheMode to use. - * - * @see #getCacheMode() - */ + @Override public Query setCacheMode(CacheMode cacheMode); - /** - * Are the results of this query eligible for second level query caching? This is different that second level - * caching of any returned entities and collections. - * - * NOTE: the query being "eligible" for caching does not necessarily mean its results will be cached. Second level - * query caching still has to be enabled on the {@link SessionFactory} for this to happen. Usually that is - * controlled by the {@code hibernate.cache.use_query_cache} configuration setting. - * - * @return {@code true} if the query results are eligible for caching, {@code false} otherwise. - * - * @see org.hibernate.cfg.AvailableSettings#USE_QUERY_CACHE - */ - public boolean isCacheable(); - - /** - * Enable/disable second level query (result) caching for this query. - * - * @param cacheable Should the query results be cacheable? - * - * @see #isCacheable - */ + @Override public Query setCacheable(boolean cacheable); - /** - * Obtain the name of the second level query cache region in which query results will be stored (if they are - * cached, see the discussion on {@link #isCacheable()} for more information). {@code null} indicates that the - * default region should be used. - * - * @return The specified cache region name into which query results should be placed; {@code null} indicates - * the default region. - */ - public String getCacheRegion(); - - /** - * Set the name of the cache region where query results should be cached (if cached at all). - * - * @param cacheRegion the name of a query cache region, or {@code null} to indicate that the default region - * should be used. - * - * @see #getCacheRegion() - */ + @Override public Query setCacheRegion(String cacheRegion); - /** - * Obtain the query timeout in seconds. This value is eventually passed along to the JDBC query via - * {@link java.sql.Statement#setQueryTimeout(int)}. Zero indicates no timeout. - * - * @return The timeout in seconds - * - * @see java.sql.Statement#getQueryTimeout() - * @see java.sql.Statement#setQueryTimeout(int) - */ - public Integer getTimeout(); - - /** - * Set the query timeout in seconds. - * - * NOTE it is important to understand that any value set here is eventually passed directly through to the JDBC - * Statement which expressly disallows negative values. So negative values should be avoided as a general rule. - * - * @param timeout the timeout in seconds - * - * @see #getTimeout() - */ + @Override public Query setTimeout(int timeout); - /** - * Obtain the JDBC fetch size hint in effect for this query. This value is eventually passed along to the JDBC - * query via {@link java.sql.Statement#setFetchSize(int)}. As defined b y JDBC, this value is a hint to the - * driver to indicate how many rows to fetch from the database when more rows are needed. - * - * NOTE : JDBC expressly defines this value as a hint. It may or may not have any effect on the actual - * query execution and ResultSet processing depending on the driver. - * - * @return The timeout in seconds - * - * @see java.sql.Statement#getFetchSize() - * @see java.sql.Statement#setFetchSize(int) - */ - public Integer getFetchSize(); - - /** - * Sets a JDBC fetch size hint for the query. - * - * @param fetchSize the fetch size hint - * - * @see #getFetchSize() - */ + @Override public Query setFetchSize(int fetchSize); + @Override + public Query setReadOnly(boolean readOnly); + /** * Obtains the LockOptions in effect for this query. * @@ -310,54 +198,6 @@ public interface Query { */ public Query setComment(String comment); - /** - * Should entities and proxies loaded by this Query be put in read-only mode? If the - * read-only/modifiable setting was not initialized, then the default - * read-only/modifiable setting for the persistence context is returned instead. - * @see Query#setReadOnly(boolean) - * @see org.hibernate.engine.spi.PersistenceContext#isDefaultReadOnly() - * - * The read-only/modifiable setting has no impact on entities/proxies returned by the - * query that existed in the session before the query was executed. - * - * @return true, entities and proxies loaded by the query will be put in read-only mode - * false, entities and proxies loaded by the query will be put in modifiable mode - */ - public boolean isReadOnly(); - - /** - * Set the read-only/modifiable mode for entities and proxies - * loaded by this Query. This setting overrides the default setting - * for the persistence context. - * @see org.hibernate.engine.spi.PersistenceContext#isDefaultReadOnly() - * - * To set the default read-only/modifiable setting used for - * entities and proxies that are loaded into the session: - * @see org.hibernate.engine.spi.PersistenceContext#setDefaultReadOnly(boolean) - * @see org.hibernate.Session#setDefaultReadOnly(boolean) - * - * Read-only entities are not dirty-checked and snapshots of persistent - * state are not maintained. Read-only entities can be modified, but - * changes are not persisted. - * - * When a proxy is initialized, the loaded entity will have the same - * read-only/modifiable setting as the uninitialized - * proxy has, regardless of the session's current setting. - * - * The read-only/modifiable setting has no impact on entities/proxies - * returned by the query that existed in the session before the query was executed. - * - * @param readOnly true, entities and proxies loaded by the query will be put in read-only mode - * false, entities and proxies loaded by the query will be put in modifiable mode - */ - public Query setReadOnly(boolean readOnly); - - /** - * Return the Hibernate types of the query result set. - * @return an array of types - */ - public Type[] getReturnTypes() throws HibernateException; - /** * Return the HQL select clause aliases (if any) * @return an array of aliases as strings diff --git a/hibernate-core/src/main/java/org/hibernate/SQLQuery.java b/hibernate-core/src/main/java/org/hibernate/SQLQuery.java index 6cc40ae0c8..4938546a27 100755 --- a/hibernate-core/src/main/java/org/hibernate/SQLQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/SQLQuery.java @@ -22,7 +22,6 @@ * Boston, MA 02110-1301 USA */ package org.hibernate; -import java.util.Collection; import java.util.List; import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; @@ -48,54 +47,15 @@ import org.hibernate.type.Type; * @author Gavin King * @author Steve Ebersole */ -public interface SQLQuery extends Query { - /** - * Obtain the list of query spaces (table names) the query is synchronized on. These spaces affect the process - * of auto-flushing by determining which entities will be processed by auto-flush based on the table to - * which those entities are mapped and which are determined to have pending state changes. - * - * @return The list of query spaces upon which the query is synchronized. - */ - public Collection getSynchronizedQuerySpaces(); +public interface SQLQuery extends Query, SynchronizeableQuery { + @Override + SQLQuery addSynchronizedQuerySpace(String querySpace); - /** - * 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 - * - * @see #getSynchronizedQuerySpaces() - */ - public SQLQuery addSynchronizedQuerySpace(String querySpace); + @Override + SQLQuery addSynchronizedEntityName(String entityName) throws MappingException; - /** - * 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 - * - * @see #getSynchronizedQuerySpaces() - */ - public SQLQuery addSynchronizedEntityName(String entityName) throws MappingException; - - /** - * 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 - * - * @see #getSynchronizedQuerySpaces() - */ - public SQLQuery addSynchronizedEntityClass(Class entityClass) throws MappingException; + @Override + SQLQuery addSynchronizedEntityClass(Class entityClass) throws MappingException; /** * Use a predefined named result-set mapping. This might be defined by a {@code } element in a diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index 125ac0563c..b962573da5 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -84,6 +84,35 @@ public interface SharedSessionContract extends Serializable { */ public SQLQuery createSQLQuery(String queryString); + /** + * Creates a call to a stored procedure. + * + * @param procedureName The name of the procedure. + * + * @return The representation of the procedure call. + */ + public StoredProcedureCall createStoredProcedureCall(String procedureName); + + /** + * Creates a call to a stored procedure with specific result set entity mappings + * + * @param procedureName The name of the procedure. + * @param resultClasses The entity(s) to map the result on to. + * + * @return The representation of the procedure call. + */ + public StoredProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses); + + /** + * Creates a call to a stored procedure with specific result set entity mappings + * + * @param procedureName The name of the procedure. + * @param resultSetMappings The explicit result set mapping(s) to use for mapping the results + * + * @return The representation of the procedure call. + */ + public StoredProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings); + /** * Create {@link Criteria} instance for the given class (entity or subclasses/implementors) * @@ -122,4 +151,5 @@ public interface SharedSessionContract extends Serializable { * @return The criteria instance for manipulation and execution */ public Criteria createCriteria(String entityName, String alias); + } diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureCall.java b/hibernate-core/src/main/java/org/hibernate/StoredProcedureCall.java new file mode 100644 index 0000000000..4d5bfd1e8e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/StoredProcedureCall.java @@ -0,0 +1,197 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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 javax.persistence.ParameterMode; +import javax.persistence.TemporalType; +import java.util.List; + +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public interface StoredProcedureCall extends BasicQueryContract, SynchronizeableQuery { + @Override + StoredProcedureCall addSynchronizedQuerySpace(String querySpace); + + @Override + StoredProcedureCall addSynchronizedEntityName(String entityName) throws MappingException; + + @Override + StoredProcedureCall addSynchronizedEntityClass(Class entityClass) throws MappingException; + + /** + * Get the name of the stored procedure to be called. + * + * @return The procedure name. + */ + public String getProcedureName(); + + + /** + * Register a positional parameter. + * All positional parameters must be registered. + * + * @param position parameter position + * @param type type of the parameter + * @param mode parameter mode + * + * @return the same query instance + */ + StoredProcedureCall registerStoredProcedureParameter( + int position, + Class type, + ParameterMode mode); + + /** + * Register a named parameter. + * When using parameter names, all parameters must be registered + * in the order in which they occur in the parameter list of the + * stored procedure. + * + * @param parameterName name of the parameter as registered or + *

+ * specified in metadata + * @param type type of the parameter + * @param mode parameter mode + * + * @return the same query instance + */ + StoredProcedureCall registerStoredProcedureParameter( + String parameterName, + Class type, + ParameterMode mode); + + /** + * Retrieve all registered parameters. + * + * @return The (immutable) list of all registered parameters. + */ + public List getRegisteredParameters(); + + /** + * Retrieve parameter registered by name. + * + * @param name The name under which the parameter of interest was registered. + * + * @return The registered parameter. + */ + public StoredProcedureParameter getRegisteredParameter(String name); + public StoredProcedureParameter getRegisteredParameter(int position); + + public StoredProcedureOutputs getOutputs(); + + /** + * Describes a parameter registered with the stored procedure. Parameters can be either named or positional + * as the registration mechanism. Named and positional should not be mixed. + */ + public static interface StoredProcedureParameter { + /** + * The name under which this parameter was registered. Can be {@code null} which should indicate that + * positional registration was used (and therefore {@link #getPosition()} should return non-null. + * + * @return The name; + */ + public String getName(); + + /** + * The position at which this parameter was registered. Can be {@code null} which should indicate that + * named registration was used (and therefore {@link #getName()} should return non-null. + * + * @return The name; + */ + public Integer getPosition(); + + /** + * Obtain the Java type of parameter. This is used to guess the Hibernate type (unless {@link #setHibernateType} + * is called explicitly). + * + * @return The parameter Java type. + */ + public Class getType(); + + /** + * Retrieves the parameter "mode" which describes how the parameter is defined in the actual database procedure + * definition (is it an INPUT parameter? An OUTPUT parameter? etc). + * + * @return The parameter mode. + */ + public ParameterMode getMode(); + + /** + * Set the Hibernate mapping type for this parameter. + * + * @param type The Hibernate mapping type. + */ + public void setHibernateType(Type type); + + /** + * Retrieve the binding associated with this parameter. The binding is only relevant for INPUT parameters. Can + * return {@code null} if nothing has been bound yet. To bind a value to the parameter use one of the + * {@link #bindValue} methods. + * + * @return The parameter binding + */ + public StoredProcedureParameterBind getParameterBind(); + + /** + * Bind a value to the parameter. How this value is bound to the underlying JDBC CallableStatement is + * totally dependent on the Hibernate type. + * + * @param value The value to bind. + */ + public void bindValue(T value); + + /** + * Bind a value to the parameter, using just a specified portion of the DATE/TIME value. It is illegal to call + * this form if the parameter is not DATE/TIME type. The Hibernate type is circumvented in this case and + * an appropriate "precision" Type is used instead. + * + * @param value The value to bind + * @param explicitTemporalType An explicitly supplied TemporalType. + */ + public void bindValue(T value, TemporalType explicitTemporalType); + } + + /** + * Describes an input value binding for any IN/INOUT parameters. + */ + public static interface StoredProcedureParameterBind { + /** + * Retrieves the bound value. + * + * @return The bound value. + */ + public T getValue(); + + /** + * If {@code } represents a DATE/TIME type value, JPA usually allows specifying the particular parts of + * the DATE/TIME value to be bound. This value represents the particular part the user requested to be bound. + * + * @return The explicitly supplied TemporalType. + */ + public TemporalType getExplicitTemporalType(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureOutputs.java b/hibernate-core/src/main/java/org/hibernate/StoredProcedureOutputs.java new file mode 100644 index 0000000000..7591c7d415 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/StoredProcedureOutputs.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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; + +/** + * Represents all the outputs of a call to a database stored procedure (or function) through the JDBC + * {@link java.sql.CallableStatement} interface. + * + * @author Steve Ebersole + */ +public interface StoredProcedureOutputs { + /** + * Retrieve the value of an OUTPUT parameter by the name under which the parameter was registered. + * + * @param name The name under which the parameter was registered. + * + * @return The output value. + * + * @see StoredProcedureCall#registerStoredProcedureParameter(String, Class, javax.persistence.ParameterMode) + */ + public Object getOutputParameterValue(String name); + + /** + * Retrieve the value of an OUTPUT parameter by the name position under which the parameter was registered. + * + * @param position The position at which the parameter was registered. + * + * @return The output value. + * + * @see StoredProcedureCall#registerStoredProcedureParameter(int, Class, javax.persistence.ParameterMode) + */ + public Object getOutputParameterValue(int position); + + /** + * Are there any more returns associated with this set of outputs? + * + * @return {@code true} means there are more results available via {@link #getNextReturn()}; {@code false} + * indicates that calling {@link #getNextReturn()} will certainly result in an exception. + */ + public boolean hasMoreReturns(); + + /** + * Retrieve the next return. + * + * @return The next return. + */ + public StoredProcedureReturn getNextReturn(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureResultSetReturn.java b/hibernate-core/src/main/java/org/hibernate/StoredProcedureResultSetReturn.java new file mode 100644 index 0000000000..70b904c26b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/StoredProcedureResultSetReturn.java @@ -0,0 +1,48 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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 java.util.List; + +/** + * Models a stored procedure result that is a result set. + * + * @author Steve Ebersole + */ +public interface StoredProcedureResultSetReturn extends StoredProcedureReturn { + /** + * Consume the underlying {@link java.sql.ResultSet} and return the resulting List. + * + * @return The consumed ResultSet values. + */ + public List getResultList(); + + /** + * Consume the underlying {@link java.sql.ResultSet} with the expectation that there is just a single level of + * root returns. + * + * @return The single result. + */ + public Object getSingleResult(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureReturn.java b/hibernate-core/src/main/java/org/hibernate/StoredProcedureReturn.java new file mode 100644 index 0000000000..d14c4e11ad --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/StoredProcedureReturn.java @@ -0,0 +1,31 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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; + +/** + * @author Steve Ebersole + */ +public interface StoredProcedureReturn { + public boolean isResultSet(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/StoredProcedureUpdateCountReturn.java b/hibernate-core/src/main/java/org/hibernate/StoredProcedureUpdateCountReturn.java new file mode 100644 index 0000000000..e4a9324489 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/StoredProcedureUpdateCountReturn.java @@ -0,0 +1,31 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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; + +/** + * @author Steve Ebersole + */ +public interface StoredProcedureUpdateCountReturn extends StoredProcedureReturn { + public int getUpdateCount(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java b/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java new file mode 100644 index 0000000000..fa8239dd6a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/SynchronizeableQuery.java @@ -0,0 +1,79 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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 java.util.Collection; + +/** + * @author Steve Ebersole + */ +public interface SynchronizeableQuery { + /** + * Obtain the list of query spaces (table names) the query is synchronized on. These spaces affect the process + * of auto-flushing by determining which entities will be processed by auto-flush based on the table to + * which those entities are mapped and which are determined to have pending state changes. + * + * @return The list of query spaces upon which the query is synchronized. + */ + public Collection getSynchronizedQuerySpaces(); + + /** + * 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 + * + * @see #getSynchronizedQuerySpaces() + */ + public SynchronizeableQuery addSynchronizedQuerySpace(String querySpace); + + /** + * 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 + * + * @see #getSynchronizedQuerySpaces() + */ + public SynchronizeableQuery addSynchronizedEntityName(String entityName) throws MappingException; + + /** + * 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 + * + * @see #getSynchronizedQuerySpaces() + */ + public SynchronizeableQuery addSynchronizedEntityClass(Class entityClass) throws MappingException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractBasicQueryContractImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractBasicQueryContractImpl.java new file mode 100644 index 0000000000..f6d32d155f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractBasicQueryContractImpl.java @@ -0,0 +1,146 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.internal; + +import java.io.Serializable; +import java.util.Map; + +import org.hibernate.BasicQueryContract; +import org.hibernate.CacheMode; +import org.hibernate.FlushMode; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.RowSelection; +import org.hibernate.engine.spi.SessionImplementor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractBasicQueryContractImpl implements BasicQueryContract { + private final SessionImplementor session; + + private FlushMode flushMode; + private CacheMode cacheMode; + + private boolean cacheable; + private String cacheRegion; + private boolean readOnly; + private RowSelection selection = new RowSelection(); + + protected AbstractBasicQueryContractImpl(SessionImplementor session) { + this.session = session; + this.readOnly = session.getPersistenceContext().isDefaultReadOnly(); + } + + protected SessionImplementor session() { + return session; + } + + @Override + public FlushMode getFlushMode() { + return flushMode; + } + + @Override + public BasicQueryContract setFlushMode(FlushMode flushMode) { + this.flushMode = flushMode; + return this; + } + + @Override + public CacheMode getCacheMode() { + return cacheMode; + } + + @Override + public BasicQueryContract setCacheMode(CacheMode cacheMode) { + this.cacheMode = cacheMode; + return this; + } + + @Override + public boolean isCacheable() { + return cacheable; + } + + @Override + public BasicQueryContract setCacheable(boolean cacheable) { + this.cacheable = cacheable; + return this; + } + + @Override + public String getCacheRegion() { + return cacheRegion; + } + + @Override + public BasicQueryContract setCacheRegion(String cacheRegion) { + if ( cacheRegion != null ) { + this.cacheRegion = cacheRegion.trim(); + } + return this; + } + + @Override + public boolean isReadOnly() { + return readOnly; + } + + @Override + public BasicQueryContract setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + return this; + } + + @Override + public Integer getTimeout() { + return selection.getTimeout(); + } + + @Override + public BasicQueryContract setTimeout(int timeout) { + selection.setTimeout( timeout ); + return this; + } + + @Override + public Integer getFetchSize() { + return selection.getFetchSize(); + } + + @Override + public BasicQueryContract setFetchSize(int fetchSize) { + selection.setFetchSize( fetchSize ); + return this; + } + + public QueryParameters buildQueryParametersObject() { + QueryParameters qp = new QueryParameters(); + qp.setRowSelection( selection ); + qp.setCacheable( cacheable ); + qp.setCacheRegion( cacheRegion ); + qp.setReadOnly( readOnly ); + return qp; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java index 82215386e0..7e14ce91ba 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java @@ -36,6 +36,7 @@ import org.hibernate.SQLQuery; import org.hibernate.ScrollableResults; import org.hibernate.SessionException; import org.hibernate.SharedSessionContract; +import org.hibernate.StoredProcedureCall; import org.hibernate.cache.spi.CacheKey; import org.hibernate.engine.jdbc.LobCreationContext; import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; @@ -235,6 +236,33 @@ public abstract class AbstractSessionImpl implements Serializable, SharedSession return query; } + @Override + @SuppressWarnings("UnnecessaryLocalVariable") + public StoredProcedureCall createStoredProcedureCall(String procedureName) { + errorIfClosed(); + final StoredProcedureCall call = new StoredProcedureCallImpl( this, procedureName ); +// call.setComment( "Dynamic stored procedure call" ); + return call; + } + + @Override + @SuppressWarnings("UnnecessaryLocalVariable") + public StoredProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { + errorIfClosed(); + final StoredProcedureCall call = new StoredProcedureCallImpl( this, procedureName, resultClasses ); +// call.setComment( "Dynamic stored procedure call" ); + return call; + } + + @Override + @SuppressWarnings("UnnecessaryLocalVariable") + public StoredProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { + errorIfClosed(); + final StoredProcedureCall call = new StoredProcedureCallImpl( this, procedureName, resultSetMappings ); +// call.setComment( "Dynamic stored procedure call" ); + return call; + } + protected HQLQueryPlan getHQLQueryPlan(String query, boolean shallow) throws HibernateException { return factory.getQueryPlanCache().getHQLQueryPlan( query, shallow, getEnabledFilters() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SQLQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SQLQueryImpl.java index 2f380a8d30..657ab3376b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SQLQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SQLQueryImpl.java @@ -31,7 +31,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.LockOptions; @@ -101,44 +100,15 @@ public class SQLQueryImpl extends AbstractQueryImpl implements SQLQuery { this.callable = queryDef.isCallable(); } - SQLQueryImpl( - final String sql, - final String returnAliases[], - final Class returnClasses[], - final LockMode[] lockModes, - final SessionImplementor session, - final Collection 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 ); - for ( int i=0; i(); - querySpaces = null; - callable = false; + this.queryReturns = new ArrayList(); + this.querySpaces = null; + this.callable = callable; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 2a30488f6d..52824ed5e0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -73,6 +73,7 @@ import org.hibernate.ScrollableResults; import org.hibernate.Session; import org.hibernate.SessionBuilder; import org.hibernate.SessionException; +import org.hibernate.StoredProcedureCall; import org.hibernate.engine.spi.SessionOwner; import org.hibernate.SharedSessionBuilder; import org.hibernate.SimpleNaturalIdLoadAccess; @@ -1742,6 +1743,27 @@ public final class SessionImpl extends AbstractSessionImpl implements EventSourc return super.createSQLQuery( sql ); } + @Override + public StoredProcedureCall createStoredProcedureCall(String procedureName) { + errorIfClosed(); + checkTransactionSynchStatus(); + return super.createStoredProcedureCall( procedureName ); + } + + @Override + public StoredProcedureCall createStoredProcedureCall(String procedureName, String... resultSetMappings) { + errorIfClosed(); + checkTransactionSynchStatus(); + return super.createStoredProcedureCall( procedureName, resultSetMappings ); + } + + @Override + public StoredProcedureCall createStoredProcedureCall(String procedureName, Class... resultClasses) { + errorIfClosed(); + checkTransactionSynchStatus(); + return super.createStoredProcedureCall( procedureName, resultClasses ); + } + public ScrollableResults scrollCustomQuery(CustomQuery customQuery, QueryParameters queryParameters) throws HibernateException { errorIfClosed(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureCallImpl.java new file mode 100644 index 0000000000..ee93509086 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureCallImpl.java @@ -0,0 +1,555 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.internal; + +import javax.persistence.ParameterMode; +import javax.persistence.TemporalType; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jboss.logging.Logger; + +import org.hibernate.HibernateException; +import org.hibernate.LockMode; +import org.hibernate.MappingException; +import org.hibernate.QueryException; +import org.hibernate.StoredProcedureCall; +import org.hibernate.StoredProcedureOutputs; +import org.hibernate.cfg.NotYetImplementedException; +import org.hibernate.engine.ResultSetMappingDefinition; +import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; +import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.DateType; +import org.hibernate.type.ProcedureParameterExtractionAware; +import org.hibernate.type.Type; + +/** + * @author Steve Ebersole + */ +public class StoredProcedureCallImpl extends AbstractBasicQueryContractImpl implements StoredProcedureCall { + private static final Logger log = Logger.getLogger( StoredProcedureCallImpl.class ); + + private final String procedureName; + private final NativeSQLQueryReturn[] queryReturns; + + private TypeOfParameter typeOfParameters = TypeOfParameter.UNKNOWN; + private List registeredParameters = new ArrayList(); + + private Set synchronizedQuerySpaces; + + @SuppressWarnings("unchecked") + public StoredProcedureCallImpl(SessionImplementor session, String procedureName) { + this( session, procedureName, (List) null ); + } + + public StoredProcedureCallImpl(SessionImplementor session, String procedureName, List queryReturns) { + super( session ); + this.procedureName = procedureName; + + if ( queryReturns == null || queryReturns.isEmpty() ) { + this.queryReturns = new NativeSQLQueryReturn[0]; + } + else { + this.queryReturns = queryReturns.toArray( new NativeSQLQueryReturn[ queryReturns.size() ] ); + } + } + + public StoredProcedureCallImpl(SessionImplementor session, String procedureName, Class... resultClasses) { + this( session, procedureName, collectQueryReturns( resultClasses ) ); + } + + private static List collectQueryReturns(Class[] resultClasses) { + if ( resultClasses == null || resultClasses.length == 0 ) { + return null; + } + + List queryReturns = new ArrayList( resultClasses.length ); + int i = 1; + for ( Class resultClass : resultClasses ) { + queryReturns.add( new NativeSQLQueryRootReturn( "alias" + i, resultClass.getName(), LockMode.READ ) ); + i++; + } + return queryReturns; + } + + public StoredProcedureCallImpl(SessionImplementor session, String procedureName, String... resultSetMappings) { + this( session, procedureName, collectQueryReturns( session, resultSetMappings ) ); + } + + private static List collectQueryReturns(SessionImplementor session, String[] resultSetMappings) { + if ( resultSetMappings == null || resultSetMappings.length == 0 ) { + return null; + } + + List queryReturns = new ArrayList( resultSetMappings.length ); + for ( String resultSetMapping : resultSetMappings ) { + ResultSetMappingDefinition mapping = session.getFactory().getResultSetMapping( resultSetMapping ); + if ( mapping == null ) { + throw new MappingException( "Unknown SqlResultSetMapping [" + resultSetMapping + "]" ); + } + queryReturns.addAll( Arrays.asList( mapping.getQueryReturns() ) ); + } + return queryReturns; + } + +// public StoredProcedureCallImpl( +// SessionImplementor session, +// String procedureName, +// List parameters) { +// // this form is intended for named stored procedure calls. +// // todo : introduce a NamedProcedureCallDefinition object to hold all needed info and pass that in here; will help with EM.addNamedQuery as well.. +// this( session, procedureName ); +// for ( StoredProcedureParameter parameter : parameters ) { +// registerParameter( (StoredProcedureParameterImplementor) parameter ); +// } +// } + + @Override + public String getProcedureName() { + return procedureName; + } + + NativeSQLQueryReturn[] getQueryReturns() { + return queryReturns; + } + + @Override + @SuppressWarnings("unchecked") + public StoredProcedureCall registerStoredProcedureParameter(int position, Class type, ParameterMode mode) { + registerParameter( new PositionalStoredProcedureParameter( this, position, mode, type ) ); + return this; + } + + private void registerParameter(StoredProcedureParameterImplementor parameter) { + if ( StringHelper.isNotEmpty( parameter.getName() ) ) { + if ( typeOfParameters == TypeOfParameter.POSITIONAL ) { + throw new QueryException( "Cannot mix named and positional parameters" ); + } + typeOfParameters = TypeOfParameter.NAMED; + registeredParameters.add( parameter ); + } + else if ( parameter.getPosition() != null ) { + if ( typeOfParameters == TypeOfParameter.NAMED ) { + throw new QueryException( "Cannot mix named and positional parameters" ); + } + typeOfParameters = TypeOfParameter.POSITIONAL; + registeredParameters.add( parameter.getPosition(), parameter ); + } + else { + throw new IllegalArgumentException( "Given parameter did not define name nor position [" + parameter + "]" ); + } + } + + @Override + @SuppressWarnings("unchecked") + public StoredProcedureCall registerStoredProcedureParameter(String name, Class type, ParameterMode mode) { + registerParameter( new NamedStoredProcedureParameter( this, name, mode, type ) ); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public List getRegisteredParameters() { + return registeredParameters; + } + + @Override + public StoredProcedureParameterImplementor getRegisteredParameter(String name) { + if ( typeOfParameters != TypeOfParameter.NAMED ) { + throw new IllegalArgumentException( "Names were not used to register parameters with this stored procedure call" ); + } + for ( StoredProcedureParameterImplementor parameter : registeredParameters ) { + if ( name.equals( parameter.getName() ) ) { + return parameter; + } + } + throw new IllegalArgumentException( "Could not locate parameter registered under that name [" + name + "]" ); } + + @Override + public StoredProcedureParameterImplementor getRegisteredParameter(int position) { + try { + return registeredParameters.get( position ); + } + catch ( Exception e ) { + throw new QueryException( "Could not locate parameter registered using that position [" + position + "]" ); + } + } + + @Override + public StoredProcedureOutputs getOutputs() { + + // todo : going to need a very specialized Loader for this. + // or, might be a good time to look at splitting Loader up into: + // 1) building statement objects + // 2) executing statement objects + // 3) processing result sets + + // for now assume there are no resultClasses nor mappings defined.. + // TOTAL PROOF-OF-CONCEPT!!!!!! + + final StringBuilder buffer = new StringBuilder().append( "{call " ) + .append( procedureName ) + .append( "(" ); + String sep = ""; + for ( StoredProcedureParameterImplementor parameter : registeredParameters ) { + for ( int i = 0; i < parameter.getSqlTypes().length; i++ ) { + buffer.append( sep ).append( "?" ); + sep = ","; + } + } + buffer.append( ")}" ); + + try { + final CallableStatement statement = session().getTransactionCoordinator() + .getJdbcCoordinator() + .getLogicalConnection() + .getShareableConnectionProxy() + .prepareCall( buffer.toString() ); + + // prepare parameters + int i = 1; + for ( StoredProcedureParameterImplementor parameter : registeredParameters ) { + if ( parameter == null ) { + throw new QueryException( "Registered stored procedure parameters had gaps" ); + } + + parameter.prepare( statement, i ); + i += parameter.getSqlTypes().length; + } + + return new StoredProcedureOutputsImpl( this, statement ); + } + catch (SQLException e) { + throw session().getFactory().getSQLExceptionHelper().convert( + e, + "Error preparing CallableStatement", + getProcedureName() + ); + } + } + + + @Override + public Type[] getReturnTypes() throws HibernateException { + throw new NotYetImplementedException(); + } + + protected Set synchronizedQuerySpaces() { + if ( synchronizedQuerySpaces == null ) { + synchronizedQuerySpaces = new HashSet(); + } + return synchronizedQuerySpaces; + } + + @Override + @SuppressWarnings("unchecked") + public Collection getSynchronizedQuerySpaces() { + if ( synchronizedQuerySpaces == null ) { + return Collections.emptySet(); + } + else { + return Collections.unmodifiableSet( synchronizedQuerySpaces ); + } + } + + public Set getSynchronizedQuerySpacesSet() { + return (Set) getSynchronizedQuerySpaces(); + } + + @Override + public StoredProcedureCallImpl addSynchronizedQuerySpace(String querySpace) { + synchronizedQuerySpaces().add( querySpace ); + return this; + } + + @Override + public StoredProcedureCallImpl addSynchronizedEntityName(String entityName) { + addSynchronizedQuerySpaces( session().getFactory().getEntityPersister( entityName ) ); + return this; + } + + protected void addSynchronizedQuerySpaces(EntityPersister persister) { + synchronizedQuerySpaces().addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) ); + } + + @Override + public StoredProcedureCallImpl addSynchronizedEntityClass(Class entityClass) { + addSynchronizedQuerySpaces( session().getFactory().getEntityPersister( entityClass.getName() ) ); + return this; + } + + public QueryParameters buildQueryParametersObject() { + QueryParameters qp = super.buildQueryParametersObject(); + // both of these are for documentation purposes, they are actually handled directly... + qp.setAutoDiscoverScalarTypes( true ); + qp.setCallable( true ); + return qp; + } + + /** + * Ternary logic enum + */ + private static enum TypeOfParameter { + NAMED, + POSITIONAL, + UNKNOWN + } + + protected static interface StoredProcedureParameterImplementor extends StoredProcedureParameter { + public void prepare(CallableStatement statement, int i) throws SQLException; + + public int[] getSqlTypes(); + + public T extract(CallableStatement statement); + } + + public static abstract class AbstractStoredProcedureParameterImpl implements StoredProcedureParameterImplementor { + private final StoredProcedureCallImpl procedureCall; + + private final ParameterMode mode; + private final Class type; + + private int startIndex; + private Type hibernateType; + private int[] sqlTypes; + + private StoredProcedureParameterBindImpl bind; + + protected AbstractStoredProcedureParameterImpl( + StoredProcedureCallImpl procedureCall, + ParameterMode mode, + Class type) { + this.procedureCall = procedureCall; + this.mode = mode; + this.type = type; + + setHibernateType( session().getFactory().getTypeResolver().heuristicType( type.getName() ) ); + } + + @Override + public String getName() { + return null; + } + + @Override + public Integer getPosition() { + return null; + } + + @Override + public Class getType() { + return type; + } + + @Override + public ParameterMode getMode() { + return mode; + } + + @Override + public void setHibernateType(Type type) { + if ( type == null ) { + throw new IllegalArgumentException( "Type cannot be null" ); + } + this.hibernateType = type; + this.sqlTypes = hibernateType.sqlTypes( session().getFactory() ); + } + + protected SessionImplementor session() { + return procedureCall.session(); + } + + @Override + public void prepare(CallableStatement statement, int startIndex) throws SQLException { + if ( mode == ParameterMode.REF_CURSOR ) { + throw new NotYetImplementedException( "Support for REF_CURSOR parameters not yet supported" ); + } + + this.startIndex = startIndex; + if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { + if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { + if ( sqlTypes.length > 1 ) { + if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) + && ( (ProcedureParameterExtractionAware) hibernateType ).canDoExtraction() ) { + // the type can handle multi-param extraction... + } + else { + // it cannot... + throw new UnsupportedOperationException( + "Type [" + hibernateType + "] does support multi-parameter value extraction" + ); + } + } + for ( int i = 0; i < sqlTypes.length; i++ ) { + statement.registerOutParameter( startIndex + i, sqlTypes[i] ); + } + } + + if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { + if ( bind == null || bind.getValue() == null ) { + log.debugf( + "Stored procedure [%s] IN/INOUT parameter [%s] not bound; assuming procedure defines default value", + procedureCall.getProcedureName(), + this + ); + } + else { + final Type typeToUse; + if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.TIMESTAMP ) { + typeToUse = hibernateType; + } + else if ( bind.getExplicitTemporalType() != null && bind.getExplicitTemporalType() == TemporalType.DATE ) { + typeToUse = DateType.INSTANCE; + } + else { + typeToUse = hibernateType; + } + typeToUse.nullSafeSet( statement, bind.getValue(), startIndex, session() ); + } + } + } + } + + public int[] getSqlTypes() { + return sqlTypes; + } + + @Override + public StoredProcedureParameterBind getParameterBind() { + return bind; + } + + @Override + public void bindValue(T value) { + this.bind = new StoredProcedureParameterBindImpl( value ); + } + + @Override + public void bindValue(T value, TemporalType explicitTemporalType) { + if ( explicitTemporalType != null ) { + if ( ! isDateTimeType() ) { + throw new IllegalArgumentException( "TemporalType should not be specified for non date/time type" ); + } + } + this.bind = new StoredProcedureParameterBindImpl( value, explicitTemporalType ); + } + + private boolean isDateTimeType() { + return Date.class.isAssignableFrom( type ) + || Calendar.class.isAssignableFrom( type ); + } + + @Override + @SuppressWarnings("unchecked") + public T extract(CallableStatement statement) { + try { + if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) ) { + return (T) ( (ProcedureParameterExtractionAware) hibernateType ).extract( statement, startIndex, session() ); + } + else { + return (T) statement.getObject( startIndex ); + } + } + catch (SQLException e) { + throw procedureCall.session().getFactory().getSQLExceptionHelper().convert( + e, + "Unable to extract OUT/INOUT parameter value" + ); + } + } + } + + public static class StoredProcedureParameterBindImpl implements StoredProcedureParameterBind { + private final T value; + private final TemporalType explicitTemporalType; + + public StoredProcedureParameterBindImpl(T value) { + this( value, null ); + } + + public StoredProcedureParameterBindImpl(T value, TemporalType explicitTemporalType) { + this.value = value; + this.explicitTemporalType = explicitTemporalType; + } + + @Override + public T getValue() { + return value; + } + + @Override + public TemporalType getExplicitTemporalType() { + return explicitTemporalType; + } + } + + public static class NamedStoredProcedureParameter extends AbstractStoredProcedureParameterImpl { + private final String name; + + public NamedStoredProcedureParameter( + StoredProcedureCallImpl procedureCall, + String name, + ParameterMode mode, + Class type) { + super( procedureCall, mode, type ); + this.name = name; + } + + @Override + public String getName() { + return name; + } + } + + public static class PositionalStoredProcedureParameter extends AbstractStoredProcedureParameterImpl { + private final Integer position; + + public PositionalStoredProcedureParameter( + StoredProcedureCallImpl procedureCall, + Integer position, + ParameterMode mode, + Class type) { + super( procedureCall, mode, type ); + this.position = position; + } + + @Override + public Integer getPosition() { + return position; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureOutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureOutputsImpl.java new file mode 100644 index 0000000000..f36a7075ad --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/StoredProcedureOutputsImpl.java @@ -0,0 +1,283 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.internal; + +import java.sql.CallableStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.hibernate.JDBCException; +import org.hibernate.StoredProcedureOutputs; +import org.hibernate.StoredProcedureResultSetReturn; +import org.hibernate.StoredProcedureReturn; +import org.hibernate.StoredProcedureUpdateCountReturn; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.loader.custom.CustomLoader; +import org.hibernate.loader.custom.CustomQuery; +import org.hibernate.loader.custom.Return; +import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor; + +/** + * @author Steve Ebersole + */ +public class StoredProcedureOutputsImpl implements StoredProcedureOutputs { + private final StoredProcedureCallImpl procedureCall; + private final CallableStatement callableStatement; + + private final CustomLoaderExtension loader; + + private CurrentReturnDescriptor currentReturnDescriptor; + private boolean executed = false; + + StoredProcedureOutputsImpl(StoredProcedureCallImpl procedureCall, CallableStatement callableStatement) { + this.procedureCall = procedureCall; + this.callableStatement = callableStatement; + + // For now... + this.loader = buildSpecializedCustomLoader( procedureCall ); + } + + @Override + public Object getOutputParameterValue(String name) { + return procedureCall.getRegisteredParameter( name ).extract( callableStatement ); + } + + @Override + public Object getOutputParameterValue(int position) { + return procedureCall.getRegisteredParameter( position ).extract( callableStatement ); + } + + @Override + public boolean hasMoreReturns() { + if ( currentReturnDescriptor == null ) { + final boolean isResultSet; + + if ( executed ) { + try { + isResultSet = callableStatement.getMoreResults(); + } + catch (SQLException e) { + throw convert( e, "Error calling CallableStatement.getMoreResults" ); + } + } + else { + try { + isResultSet = callableStatement.execute(); + } + catch (SQLException e) { + throw convert( e, "Error calling CallableStatement.execute" ); + } + executed = true; + } + + int updateCount = -1; + if ( ! isResultSet ) { + try { + updateCount = callableStatement.getUpdateCount(); + } + catch (SQLException e) { + throw convert( e, "Error calling CallableStatement.getUpdateCount" ); + } + } + + currentReturnDescriptor = new CurrentReturnDescriptor( isResultSet, updateCount ); + } + + return hasMoreResults( currentReturnDescriptor ); + } + + private boolean hasMoreResults(CurrentReturnDescriptor descriptor) { + return currentReturnDescriptor.isResultSet || currentReturnDescriptor.updateCount >= 0; + } + + @Override + public StoredProcedureReturn getNextReturn() { + if ( currentReturnDescriptor == null ) { + if ( executed ) { + throw new IllegalStateException( "Unexpected condition" ); + } + else { + throw new IllegalStateException( "hasMoreReturns() not called before getNextReturn()" ); + } + } + + if ( ! hasMoreResults( currentReturnDescriptor ) ) { + throw new IllegalStateException( "Results have been exhausted" ); + } + + CurrentReturnDescriptor copyReturnDescriptor = currentReturnDescriptor; + currentReturnDescriptor = null; + + if ( copyReturnDescriptor.isResultSet ) { + try { + return new ResultSetReturn( this, callableStatement.getResultSet() ); + } + catch (SQLException e) { + throw convert( e, "Error calling CallableStatement.getResultSet" ); + } + } + else { + return new UpdateCountReturn( this, copyReturnDescriptor.updateCount ); + } + } + + protected JDBCException convert(SQLException e, String message) { + return procedureCall.session().getFactory().getSQLExceptionHelper().convert( e, message, procedureCall.getProcedureName() ); + } + + private static class CurrentReturnDescriptor { + private final boolean isResultSet; + private final int updateCount; + + private CurrentReturnDescriptor(boolean resultSet, int updateCount) { + isResultSet = resultSet; + this.updateCount = updateCount; + } + } + + private static class ResultSetReturn implements StoredProcedureResultSetReturn { + private final StoredProcedureOutputsImpl storedProcedureOutputs; + private final ResultSet resultSet; + + public ResultSetReturn(StoredProcedureOutputsImpl storedProcedureOutputs, ResultSet resultSet) { + this.storedProcedureOutputs = storedProcedureOutputs; + this.resultSet = resultSet; + } + + @Override + public boolean isResultSet() { + return true; + } + + @Override + @SuppressWarnings("unchecked") + public List getResultList() { + try { + return storedProcedureOutputs.loader.processResultSet( resultSet ); + } + catch (SQLException e) { + throw storedProcedureOutputs.convert( e, "Error calling ResultSet.next" ); + } + } + + @Override + public Object getSingleResult() { + List results = getResultList(); + if ( results == null || results.isEmpty() ) { + return null; + } + else { + return results.get( 0 ); + } + } + } + + private class UpdateCountReturn implements StoredProcedureUpdateCountReturn { + private final StoredProcedureOutputsImpl storedProcedureOutputs; + private final int updateCount; + + public UpdateCountReturn(StoredProcedureOutputsImpl storedProcedureOutputs, int updateCount) { + this.storedProcedureOutputs = storedProcedureOutputs; + this.updateCount = updateCount; + } + + @Override + public int getUpdateCount() { + return updateCount; + } + + @Override + public boolean isResultSet() { + return false; + } + } + + private static CustomLoaderExtension buildSpecializedCustomLoader(final StoredProcedureCallImpl procedureCall) { + final SQLQueryReturnProcessor processor = new SQLQueryReturnProcessor( + procedureCall.getQueryReturns(), + procedureCall.session().getFactory() + ); + processor.process(); + final List customReturns = processor.generateCustomReturns( false ); + + CustomQuery customQuery = new CustomQuery() { + @Override + public String getSQL() { + return procedureCall.getProcedureName(); + } + + @Override + public Set getQuerySpaces() { + return procedureCall.getSynchronizedQuerySpacesSet(); + } + + @Override + public Map getNamedParameterBindPoints() { + // no named parameters in terms of embedded in the SQL string + return null; + } + + @Override + public List getCustomQueryReturns() { + return customReturns; + } + }; + + return new CustomLoaderExtension( + customQuery, + procedureCall.buildQueryParametersObject(), + procedureCall.session() + ); + } + + private static class CustomLoaderExtension extends CustomLoader { + private QueryParameters queryParameters; + private SessionImplementor session; + + public CustomLoaderExtension( + CustomQuery customQuery, + QueryParameters queryParameters, + SessionImplementor session) { + super( customQuery, session.getFactory() ); + this.queryParameters = queryParameters; + this.session = session; + } + + public List processResultSet(ResultSet resultSet) throws SQLException { + super.autoDiscoverTypes( resultSet ); + return super.processResultSet( + resultSet, + queryParameters, + session, + true, + null, + Integer.MAX_VALUE + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index 486c62ca57..968d0e899b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -28,6 +28,7 @@ import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -668,12 +669,9 @@ public abstract class Loader { applyPostLoadLocks( row, lockModesArray, session ); - return forcedResultTransformer == null ? - getResultColumnOrRow( row, queryParameters.getResultTransformer(), resultSet, session ) : - forcedResultTransformer.transformTuple( - getResultRow( row, resultSet, session ), - getResultRowAliases() - ) + return forcedResultTransformer == null + ? getResultColumnOrRow( row, queryParameters.getResultTransformer(), resultSet, session ) + : forcedResultTransformer.transformTuple( getResultRow( row, resultSet, session ), getResultRowAliases() ) ; } @@ -825,11 +823,8 @@ public abstract class Loader { selection.getMaxRows() : Integer.MAX_VALUE; - final int entitySpan = getEntityPersisters().length; - - final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 ); final ResultSet rs = executeQueryStatement( queryParameters, false, session ); - final PreparedStatement st = (PreparedStatement) rs.getStatement(); + final Statement st = rs.getStatement(); // would be great to move all this below here into another method that could also be used // from the new scrolling stuff. @@ -837,47 +832,59 @@ public abstract class Loader { // Would need to change the way the max-row stuff is handled (i.e. behind an interface) so // that I could do the control breaking at the means to know when to stop - final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session ); - final LockMode[] lockModesArray = getLockModes( queryParameters.getLockOptions() ); - final boolean createSubselects = isSubselectLoadingEnabled(); - final List subselectResultKeys = createSubselects ? new ArrayList() : null; - final List results = new ArrayList(); - try { - handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session ); - EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row - LOG.trace( "Processing result set" ); - int count; - for ( count = 0; count < maxRows && rs.next(); count++ ) { - LOG.debugf( "Result set row: %s", count ); - Object result = getRowFromResultSet( - rs, - session, - queryParameters, - lockModesArray, - optionalObjectKey, - hydratedObjects, - keys, - returnProxies, - forcedResultTransformer - ); - results.add( result ); - if ( createSubselects ) { - subselectResultKeys.add(keys); - keys = new EntityKey[entitySpan]; //can't reuse in this case - } - } - - LOG.tracev( "Done processing result set ({0} rows)", count ); - + return processResultSet( rs, queryParameters, session, returnProxies, forcedResultTransformer, maxRows ); } finally { st.close(); } + + } + + protected List processResultSet( + ResultSet rs, + QueryParameters queryParameters, + SessionImplementor session, + boolean returnProxies, + ResultTransformer forcedResultTransformer, + int maxRows) throws SQLException { + final int entitySpan = getEntityPersisters().length; + final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session ); + final LockMode[] lockModesArray = getLockModes( queryParameters.getLockOptions() ); + final boolean createSubselects = isSubselectLoadingEnabled(); + final List subselectResultKeys = createSubselects ? new ArrayList() : null; + final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 ); + final List results = new ArrayList(); + + handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session ); + EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row + LOG.trace( "Processing result set" ); + int count; + for ( count = 0; count < maxRows && rs.next(); count++ ) { + LOG.debugf( "Result set row: %s", count ); + Object result = getRowFromResultSet( + rs, + session, + queryParameters, + lockModesArray, + optionalObjectKey, + hydratedObjects, + keys, + returnProxies, + forcedResultTransformer + ); + results.add( result ); + if ( createSubselects ) { + subselectResultKeys.add(keys); + keys = new EntityKey[entitySpan]; //can't reuse in this case + } + } + + LOG.tracev( "Done processing result set ({0} rows)", count ); + initializeEntitiesAndCollections( hydratedObjects, rs, session, queryParameters.isReadOnly( session ) ); if ( createSubselects ) createSubselects( subselectResultKeys, queryParameters, session ); return results; - } protected boolean isSubselectLoadingEnabled() { @@ -1060,8 +1067,11 @@ public abstract class Loader { * This empty implementation merely returns its first argument. This is * overridden by some subclasses. */ - protected Object getResultColumnOrRow(Object[] row, ResultTransformer transformer, ResultSet rs, SessionImplementor session) - throws SQLException, HibernateException { + protected Object getResultColumnOrRow( + Object[] row, + ResultTransformer transformer, + ResultSet rs, + SessionImplementor session) throws SQLException, HibernateException { return row; } @@ -1069,10 +1079,10 @@ public abstract class Loader { return null; } - protected Object[] getResultRow(Object[] row, - ResultSet rs, - SessionImplementor session) - throws SQLException, HibernateException { + protected Object[] getResultRow( + Object[] row, + ResultSet rs, + SessionImplementor session) throws SQLException, HibernateException { return row; } @@ -1455,7 +1465,7 @@ public abstract class Loader { persister, key.getIdentifier(), session - ); + ); final Object object; if ( optionalObjectKey != null && key.equals( optionalObjectKey ) ) { @@ -1687,7 +1697,10 @@ public abstract class Loader { queryParameters.processFilters( getSQLString(), session ); // Applying LIMIT clause. - final LimitHandler limitHandler = getLimitHandler( queryParameters.getFilteredSQL(), queryParameters.getRowSelection() ); + final LimitHandler limitHandler = getLimitHandler( + queryParameters.getFilteredSQL(), + queryParameters.getRowSelection() + ); String sql = limitHandler.getProcessedSql(); // Adding locks and comments. diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomQuery.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomQuery.java index be3fe4eab7..ef607040cc 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/CustomQuery.java @@ -50,7 +50,7 @@ public interface CustomQuery { * * @return The query spaces */ - public Set getQuerySpaces(); + public Set getQuerySpaces(); /** * A map representing positions within the supplied {@link #getSQL query} to @@ -73,5 +73,5 @@ public interface CustomQuery { * * @return List of return descriptors. */ - public List getCustomQueryReturns(); + public List getCustomQueryReturns(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java index eb3c85323e..fe2cc4713d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java @@ -156,49 +156,49 @@ public class SQLQueryReturnProcessor { public ResultAliasContext process() { // first, break down the returns into maps keyed by alias // so that role returns can be more easily resolved to their owners - for ( int i = 0; i < queryReturns.length; i++ ) { - if ( queryReturns[i] instanceof NativeSQLQueryNonScalarReturn ) { - NativeSQLQueryNonScalarReturn rtn = ( NativeSQLQueryNonScalarReturn ) queryReturns[i]; + for ( NativeSQLQueryReturn queryReturn : queryReturns ) { + if ( queryReturn instanceof NativeSQLQueryNonScalarReturn ) { + NativeSQLQueryNonScalarReturn rtn = (NativeSQLQueryNonScalarReturn) queryReturn; alias2Return.put( rtn.getAlias(), rtn ); if ( rtn instanceof NativeSQLQueryJoinReturn ) { - NativeSQLQueryJoinReturn fetchReturn = ( NativeSQLQueryJoinReturn ) rtn; + NativeSQLQueryJoinReturn fetchReturn = (NativeSQLQueryJoinReturn) rtn; alias2OwnerAlias.put( fetchReturn.getAlias(), fetchReturn.getOwnerAlias() ); } } } // Now, process the returns - for ( int i = 0; i < queryReturns.length; i++ ) { - processReturn( queryReturns[i] ); + for ( NativeSQLQueryReturn queryReturn : queryReturns ) { + processReturn( queryReturn ); } return new ResultAliasContext(); } - public List generateCustomReturns(boolean queryHadAliases) { - List customReturns = new ArrayList(); - Map customReturnsByAlias = new HashMap(); - for ( int i = 0; i < queryReturns.length; i++ ) { - if ( queryReturns[i] instanceof NativeSQLQueryScalarReturn ) { - NativeSQLQueryScalarReturn rtn = ( NativeSQLQueryScalarReturn ) queryReturns[i]; + public List generateCustomReturns(boolean queryHadAliases) { + List customReturns = new ArrayList(); + Map customReturnsByAlias = new HashMap(); + for ( NativeSQLQueryReturn queryReturn : queryReturns ) { + if ( queryReturn instanceof NativeSQLQueryScalarReturn ) { + NativeSQLQueryScalarReturn rtn = (NativeSQLQueryScalarReturn) queryReturn; customReturns.add( new ScalarReturn( rtn.getType(), rtn.getColumnAlias() ) ); } - else if ( queryReturns[i] instanceof NativeSQLQueryRootReturn ) { - NativeSQLQueryRootReturn rtn = ( NativeSQLQueryRootReturn ) queryReturns[i]; + else if ( queryReturn instanceof NativeSQLQueryRootReturn ) { + NativeSQLQueryRootReturn rtn = (NativeSQLQueryRootReturn) queryReturn; String alias = rtn.getAlias(); EntityAliases entityAliases; if ( queryHadAliases || hasPropertyResultMap( alias ) ) { entityAliases = new DefaultEntityAliases( - ( Map ) entityPropertyResultMaps.get( alias ), - ( SQLLoadable ) alias2Persister.get( alias ), - ( String ) alias2Suffix.get( alias ) + (Map) entityPropertyResultMaps.get( alias ), + (SQLLoadable) alias2Persister.get( alias ), + (String) alias2Suffix.get( alias ) ); } else { entityAliases = new ColumnEntityAliases( - ( Map ) entityPropertyResultMaps.get( alias ), - ( SQLLoadable ) alias2Persister.get( alias ), - ( String ) alias2Suffix.get( alias ) + (Map) entityPropertyResultMaps.get( alias ), + (SQLLoadable) alias2Persister.get( alias ), + (String) alias2Suffix.get( alias ) ); } RootReturn customReturn = new RootReturn( @@ -210,37 +210,37 @@ public class SQLQueryReturnProcessor { customReturns.add( customReturn ); customReturnsByAlias.put( rtn.getAlias(), customReturn ); } - else if ( queryReturns[i] instanceof NativeSQLQueryCollectionReturn ) { - NativeSQLQueryCollectionReturn rtn = ( NativeSQLQueryCollectionReturn ) queryReturns[i]; + else if ( queryReturn instanceof NativeSQLQueryCollectionReturn ) { + NativeSQLQueryCollectionReturn rtn = (NativeSQLQueryCollectionReturn) queryReturn; String alias = rtn.getAlias(); - SQLLoadableCollection persister = ( SQLLoadableCollection ) alias2CollectionPersister.get( alias ); + SQLLoadableCollection persister = (SQLLoadableCollection) alias2CollectionPersister.get( alias ); boolean isEntityElements = persister.getElementType().isEntityType(); CollectionAliases collectionAliases; EntityAliases elementEntityAliases = null; if ( queryHadAliases || hasPropertyResultMap( alias ) ) { collectionAliases = new GeneratedCollectionAliases( - ( Map ) collectionPropertyResultMaps.get( alias ), - ( SQLLoadableCollection ) alias2CollectionPersister.get( alias ), - ( String ) alias2CollectionSuffix.get( alias ) + (Map) collectionPropertyResultMaps.get( alias ), + (SQLLoadableCollection) alias2CollectionPersister.get( alias ), + (String) alias2CollectionSuffix.get( alias ) ); if ( isEntityElements ) { elementEntityAliases = new DefaultEntityAliases( - ( Map ) entityPropertyResultMaps.get( alias ), - ( SQLLoadable ) alias2Persister.get( alias ), - ( String ) alias2Suffix.get( alias ) + (Map) entityPropertyResultMaps.get( alias ), + (SQLLoadable) alias2Persister.get( alias ), + (String) alias2Suffix.get( alias ) ); } } else { collectionAliases = new ColumnCollectionAliases( - ( Map ) collectionPropertyResultMaps.get( alias ), - ( SQLLoadableCollection ) alias2CollectionPersister.get( alias ) + (Map) collectionPropertyResultMaps.get( alias ), + (SQLLoadableCollection) alias2CollectionPersister.get( alias ) ); if ( isEntityElements ) { elementEntityAliases = new ColumnEntityAliases( - ( Map ) entityPropertyResultMaps.get( alias ), - ( SQLLoadable ) alias2Persister.get( alias ), - ( String ) alias2Suffix.get( alias ) + (Map) entityPropertyResultMaps.get( alias ), + (SQLLoadable) alias2Persister.get( alias ), + (String) alias2Suffix.get( alias ) ); } } @@ -249,46 +249,46 @@ public class SQLQueryReturnProcessor { rtn.getOwnerEntityName(), rtn.getOwnerProperty(), collectionAliases, - elementEntityAliases, + elementEntityAliases, rtn.getLockMode() ); customReturns.add( customReturn ); customReturnsByAlias.put( rtn.getAlias(), customReturn ); } - else if ( queryReturns[i] instanceof NativeSQLQueryJoinReturn ) { - NativeSQLQueryJoinReturn rtn = ( NativeSQLQueryJoinReturn ) queryReturns[i]; + else if ( queryReturn instanceof NativeSQLQueryJoinReturn ) { + NativeSQLQueryJoinReturn rtn = (NativeSQLQueryJoinReturn) queryReturn; String alias = rtn.getAlias(); FetchReturn customReturn; - NonScalarReturn ownerCustomReturn = ( NonScalarReturn ) customReturnsByAlias.get( rtn.getOwnerAlias() ); + NonScalarReturn ownerCustomReturn = (NonScalarReturn) customReturnsByAlias.get( rtn.getOwnerAlias() ); if ( alias2CollectionPersister.containsKey( alias ) ) { - SQLLoadableCollection persister = ( SQLLoadableCollection ) alias2CollectionPersister.get( alias ); + SQLLoadableCollection persister = (SQLLoadableCollection) alias2CollectionPersister.get( alias ); boolean isEntityElements = persister.getElementType().isEntityType(); CollectionAliases collectionAliases; EntityAliases elementEntityAliases = null; if ( queryHadAliases || hasPropertyResultMap( alias ) ) { collectionAliases = new GeneratedCollectionAliases( - ( Map ) collectionPropertyResultMaps.get( alias ), + (Map) collectionPropertyResultMaps.get( alias ), persister, - ( String ) alias2CollectionSuffix.get( alias ) + (String) alias2CollectionSuffix.get( alias ) ); if ( isEntityElements ) { elementEntityAliases = new DefaultEntityAliases( - ( Map ) entityPropertyResultMaps.get( alias ), - ( SQLLoadable ) alias2Persister.get( alias ), - ( String ) alias2Suffix.get( alias ) + (Map) entityPropertyResultMaps.get( alias ), + (SQLLoadable) alias2Persister.get( alias ), + (String) alias2Suffix.get( alias ) ); } } else { collectionAliases = new ColumnCollectionAliases( - ( Map ) collectionPropertyResultMaps.get( alias ), + (Map) collectionPropertyResultMaps.get( alias ), persister ); if ( isEntityElements ) { elementEntityAliases = new ColumnEntityAliases( - ( Map ) entityPropertyResultMaps.get( alias ), - ( SQLLoadable ) alias2Persister.get( alias ), - ( String ) alias2Suffix.get( alias ) + (Map) entityPropertyResultMaps.get( alias ), + (SQLLoadable) alias2Persister.get( alias ), + (String) alias2Suffix.get( alias ) ); } } @@ -297,7 +297,7 @@ public class SQLQueryReturnProcessor { ownerCustomReturn, rtn.getOwnerProperty(), collectionAliases, - elementEntityAliases, + elementEntityAliases, rtn.getLockMode() ); } @@ -305,16 +305,16 @@ public class SQLQueryReturnProcessor { EntityAliases entityAliases; if ( queryHadAliases || hasPropertyResultMap( alias ) ) { entityAliases = new DefaultEntityAliases( - ( Map ) entityPropertyResultMaps.get( alias ), - ( SQLLoadable ) alias2Persister.get( alias ), - ( String ) alias2Suffix.get( alias ) + (Map) entityPropertyResultMaps.get( alias ), + (SQLLoadable) alias2Persister.get( alias ), + (String) alias2Suffix.get( alias ) ); } else { entityAliases = new ColumnEntityAliases( - ( Map ) entityPropertyResultMaps.get( alias ), - ( SQLLoadable ) alias2Persister.get( alias ), - ( String ) alias2Suffix.get( alias ) + (Map) entityPropertyResultMaps.get( alias ), + (SQLLoadable) alias2Persister.get( alias ), + (String) alias2Suffix.get( alias ) ); } customReturn = new EntityFetchReturn( @@ -378,10 +378,6 @@ public class SQLQueryReturnProcessor { addPersister( rootReturn.getAlias(), rootReturn.getPropertyResultsMap(), persister ); } - /** - * @param propertyResult - * @param persister - */ private void addPersister(String alias, Map propertyResult, SQLLoadable persister) { alias2Persister.put( alias, persister ); String suffix = generateEntitySuffix(); diff --git a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java index 6f784a9020..f2c452c70c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/AbstractStandardBasicType.java @@ -24,6 +24,7 @@ package org.hibernate.type; import java.io.Serializable; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -48,12 +49,12 @@ import org.hibernate.type.descriptor.java.MutabilityPlan; import org.hibernate.type.descriptor.sql.SqlTypeDescriptor; /** - * TODO : javadoc + * Convenience base class for {@link BasicType} implementations * * @author Steve Ebersole */ public abstract class AbstractStandardBasicType - implements BasicType, StringRepresentableType, XmlRepresentableType { + implements BasicType, StringRepresentableType, XmlRepresentableType, ProcedureParameterExtractionAware { private static final Size DEFAULT_SIZE = new Size( 19, 2, 255, Size.LobMultiplier.NONE ); // to match legacy behavior private final Size dictatedSize = new Size(); @@ -386,4 +387,32 @@ public abstract class AbstractStandardBasicType ? getReplacement( (T) original, (T) target, session ) : target; } + + @Override + public boolean canDoExtraction() { + return true; + } + + @Override + public T extract(CallableStatement statement, int startIndex, final SessionImplementor session) throws SQLException { + // todo : have SessionImplementor extend WrapperOptions + final WrapperOptions options = new WrapperOptions() { + public boolean useStreamForLobBinding() { + return Environment.useStreamsForBinary(); + } + + public LobCreator getLobCreator() { + return Hibernate.getLobCreator( session ); + } + + public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor) { + final SqlTypeDescriptor remapped = sqlTypeDescriptor.canBeRemapped() + ? session.getFactory().getDialect().remapSqlTypeDescriptor( sqlTypeDescriptor ) + : sqlTypeDescriptor; + return remapped == null ? sqlTypeDescriptor : remapped; + } + }; + + return remapSqlTypeDescriptor( options ).getExtractor( javaTypeDescriptor ).extract( statement, startIndex, options ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java index 74310ca0b7..9c69f10b61 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/ComponentType.java @@ -25,6 +25,7 @@ package org.hibernate.type; import java.io.Serializable; import java.lang.reflect.Method; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -55,7 +56,7 @@ import org.hibernate.tuple.component.ComponentTuplizer; * * @author Gavin King */ -public class ComponentType extends AbstractType implements CompositeType { +public class ComponentType extends AbstractType implements CompositeType, ProcedureParameterExtractionAware { private final TypeFactory.TypeScope typeScope; private final String[] propertyNames; @@ -720,4 +721,55 @@ public class ComponentType extends AbstractType implements CompositeType { "Unable to locate property named " + name + " on " + getReturnedClass().getName() ); } + + private Boolean canDoExtraction; + + @Override + public boolean canDoExtraction() { + if ( canDoExtraction == null ) { + canDoExtraction = determineIfProcedureParamExtractionCanBePerformed(); + } + return canDoExtraction; + } + + private boolean determineIfProcedureParamExtractionCanBePerformed() { + for ( Type propertyType : propertyTypes ) { + if ( ! ProcedureParameterExtractionAware.class.isInstance( propertyType ) ) { + return false; + } + if ( ! ( (ProcedureParameterExtractionAware) propertyType ).canDoExtraction() ) { + return false; + } + } + return true; + } + + @Override + public Object extract(CallableStatement statement, int startIndex, SessionImplementor session) throws SQLException { + Object[] values = new Object[propertySpan]; + + int currentIndex = startIndex; + boolean notNull = false; + for ( int i = 0; i < propertySpan; i++ ) { + // we know this cast is safe from canDoExtraction + final ProcedureParameterExtractionAware propertyType = (ProcedureParameterExtractionAware) propertyTypes[i]; + final Object value = propertyType.extract( statement, currentIndex, session ); + if ( value == null ) { + if ( isKey ) { + return null; //different nullability rules for pk/fk + } + } + else { + notNull = true; + } + values[i] = value; + currentIndex += propertyType.getColumnSpan( session.getFactory() ); + } + + if ( ! notNull ) { + values = null; + } + + return resolve( values, session, null ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/PostgresUUIDType.java b/hibernate-core/src/main/java/org/hibernate/type/PostgresUUIDType.java index 3b2e3cdbf7..e199d8881a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/PostgresUUIDType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/PostgresUUIDType.java @@ -23,6 +23,7 @@ */ package org.hibernate.type; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -84,6 +85,11 @@ public class PostgresUUIDType extends AbstractSingleColumnStandardBasicType extends Type { + /** + * Can the given instance of this type actually perform the parameter value extractions? + * + * @return {@code true} indicates that @{link #extract} calls will not fail due to {@link IllegalStateException}. + */ + public boolean canDoExtraction(); + + /** + * Perform the extraction + * + * @param statement The CallableStatement from which to extract the parameter value(s). + * @param startIndex The parameter index from which to start extracting; assumes the values (if multiple) are contiguous + * @param session The originating session + * + * @return The extracted value. + * + * @throws SQLException Indicates an issue calling into the CallableStatement + * @throws IllegalStateException Thrown if this method is called on instances that return {@code false} for {@link #canDoExtraction} + */ + public T extract(CallableStatement statement, int startIndex, SessionImplementor session) throws SQLException; +} diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueExtractor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueExtractor.java index 0194cad357..4e99135566 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueExtractor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/ValueExtractor.java @@ -22,11 +22,13 @@ * Boston, MA 02110-1301 USA */ package org.hibernate.type.descriptor; + +import java.sql.CallableStatement; import java.sql.ResultSet; import java.sql.SQLException; /** - * Contract for extracting a value from a {@link ResultSet}. + * Contract for extracting value via JDBC (from {@link ResultSet} or as output param from {@link CallableStatement}). * * @author Steve Ebersole */ @@ -43,4 +45,6 @@ public interface ValueExtractor { * @throws SQLException Indicates a JDBC error occurred. */ public X extract(ResultSet rs, String name, WrapperOptions options) throws SQLException; + + public X extract(CallableStatement rs, int index, WrapperOptions options) throws SQLException; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BasicExtractor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BasicExtractor.java index 72643cc4dc..792f39d2f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BasicExtractor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BasicExtractor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -58,9 +59,7 @@ public abstract class BasicExtractor implements ValueExtractor { return sqlDescriptor; } - /** - * {@inheritDoc} - */ + @Override public J extract(ResultSet rs, String name, WrapperOptions options) throws SQLException { final J value = doExtract( rs, name, options ); if ( value == null || rs.wasNull() ) { @@ -90,4 +89,35 @@ public abstract class BasicExtractor implements ValueExtractor { * @throws SQLException Indicates a problem access the result set */ protected abstract J doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException; + + @Override + public J extract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + final J value = doExtract( statement, index, options ); + if ( value == null || statement.wasNull() ) { + LOG.tracev( "Found [null] as procedure output parameter [{0}]", index ); + return null; + } + else { + if ( LOG.isTraceEnabled() ) { + LOG.tracev( "Found [{0}] as procedure output parameter [{1}]", getJavaDescriptor().extractLoggableRepresentation( value ), index ); + } + return value; + } + } + + /** + * Perform the extraction. + *

+ * Called from {@link #extract}. Null checking of the value (as well as consulting {@link ResultSet#wasNull}) is + * done there. + * + * @param statement The callable statement containing the output parameter + * @param index The index (position) of the output parameter + * @param options The binding options + * + * @return The extracted value. + * + * @throws SQLException Indicates a problem accessing the parameter value + */ + protected abstract J doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java index e861fc0b1b..cf2103d90e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BigIntTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -65,6 +66,11 @@ public class BigIntTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getLong( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getLong( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java index a632106326..a60fa08d4a 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BitTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -68,6 +69,11 @@ public class BitTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getBoolean( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBoolean( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java index 9b61c15469..1cda8b54a1 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BlobTypeDescriptor.java @@ -24,6 +24,7 @@ package org.hibernate.type.descriptor.sql; import java.sql.Blob; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -117,6 +118,11 @@ public abstract class BlobTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getBlob( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBlob( index ), options ); + } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java index a4ae309bf4..afa54c280f 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/BooleanTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -65,6 +66,11 @@ public class BooleanTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getBoolean( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBoolean( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java index 5d9fe02696..696df0e78e 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/ClobTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.Clob; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -107,6 +108,11 @@ public abstract class ClobTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getClob( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getClob( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java index 4b6dcf6668..4bb7a702e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DateTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -66,6 +67,11 @@ public class DateTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getDate( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getDate( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java index 0431047e87..397c96f269 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DecimalTypeDescriptor.java @@ -24,6 +24,7 @@ package org.hibernate.type.descriptor.sql; import java.math.BigDecimal; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -66,6 +67,11 @@ public class DecimalTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getBigDecimal( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBigDecimal( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java index 8006dabc66..13b6a1a045 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/DoubleTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -65,6 +66,11 @@ public class DoubleTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getDouble( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getDouble( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java index 8676cfbc16..8834e2d598 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/IntegerTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -65,6 +66,11 @@ public class IntegerTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getInt( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getInt( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java index f57fcdcdb8..85c2a15559 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/RealTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -65,6 +66,11 @@ public class RealTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getFloat( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getFloat( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java index b980eca205..0786ea242c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/SmallIntTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -65,6 +66,11 @@ public class SmallIntTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getShort( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getShort( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java index 8915930f07..2b1b1b034c 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimeTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -66,6 +67,11 @@ public class TimeTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getTime( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getTime( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java index 28c903c6b5..4401becfc8 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TimestampTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -66,6 +67,11 @@ public class TimestampTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getTimestamp( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getTimestamp( index ), options ); + } }; } } \ No newline at end of file diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java index 0fa283a8aa..8418a28526 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/TinyIntTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -68,6 +69,11 @@ public class TinyIntTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getByte( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getByte( index ), options ); + } }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java index 4f75a4aa84..7c7d28a807 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarbinaryTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -63,8 +64,12 @@ public class VarbinaryTypeDescriptor implements SqlTypeDescriptor { return new BasicExtractor( javaTypeDescriptor, this ) { @Override protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { - final byte[] bytes = rs.getBytes( name ); - return javaTypeDescriptor.wrap( bytes, options ); + return javaTypeDescriptor.wrap( rs.getBytes( name ), options ); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getBytes( index ), options ); } }; } diff --git a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java index c5300fca50..50d5f3c5a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.java @@ -23,6 +23,7 @@ */ package org.hibernate.type.descriptor.sql; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -65,6 +66,11 @@ public class VarcharTypeDescriptor implements SqlTypeDescriptor { protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { return javaTypeDescriptor.wrap( rs.getString( name ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap( statement.getString( index ), options ); + } }; } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java new file mode 100644 index 0000000000..f4e54c800b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/sql/storedproc/StoredProcedureTest.java @@ -0,0 +1,116 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.test.sql.storedproc; + +import javax.persistence.Entity; +import javax.persistence.Id; +import java.util.List; + +import org.hibernate.HibernateException; +import org.hibernate.SQLQuery; +import org.hibernate.Session; +import org.hibernate.StoredProcedureCall; +import org.hibernate.StoredProcedureOutputs; +import org.hibernate.StoredProcedureResultSetReturn; +import org.hibernate.StoredProcedureReturn; +import org.hibernate.cfg.Configuration; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.engine.spi.Mapping; +import org.hibernate.mapping.AuxiliaryDatabaseObject; + +import org.junit.Before; +import org.junit.Test; + +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.junit4.ExtraAssertions; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Steve Ebersole + */ +@RequiresDialect( H2Dialect.class ) +public class StoredProcedureTest extends BaseCoreFunctionalTestCase { +// this is not working in H2 +// @Override +// protected void configure(Configuration configuration) { +// super.configure( configuration ); +// configuration.addAuxiliaryDatabaseObject( +// new AuxiliaryDatabaseObject() { +// @Override +// public void addDialectScope(String dialectName) { +// } +// +// @Override +// public boolean appliesToDialect(Dialect dialect) { +// return H2Dialect.class.isInstance( dialect ); +// } +// +// @Override +// public String sqlCreateString(Dialect dialect, Mapping p, String defaultCatalog, String defaultSchema) { +// return "CREATE ALIAS findUser AS $$\n" + +// "import org.h2.tools.SimpleResultSet;\n" + +// "import java.sql.*;\n" + +// "@CODE\n" + +// "ResultSet findUser() {\n" + +// " SimpleResultSet rs = new SimpleResultSet();\n" + +// " rs.addColumn(\"ID\", Types.INTEGER, 10, 0);\n" + +// " rs.addColumn(\"NAME\", Types.VARCHAR, 255, 0);\n" + +// " rs.addRow(1, \"Steve\");\n" + +// " return rs;\n" + +// "}\n" + +// "$$"; +// } +// +// @Override +// public String sqlDropString(Dialect dialect, String defaultCatalog, String defaultSchema) { +// return "DROP ALIAS findUser IF EXISTS"; +// } +// } +// ); +// } + + @Test + public void baseTest() { + Session session = openSession(); + session.beginTransaction(); + + StoredProcedureCall query = session.createStoredProcedureCall( "user"); + StoredProcedureOutputs outputs = query.getOutputs(); + assertTrue( "Checking StoredProcedureOutputs has more returns", outputs.hasMoreReturns() ); + StoredProcedureReturn nextReturn = outputs.getNextReturn(); + assertNotNull( nextReturn ); + ExtraAssertions.assertClassAssignability( StoredProcedureResultSetReturn.class, nextReturn.getClass() ); + StoredProcedureResultSetReturn resultSetReturn = (StoredProcedureResultSetReturn) nextReturn; + String name = (String) resultSetReturn.getSingleResult(); + assertEquals( "SA", name ); + + session.getTransaction().commit(); + session.close(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/typeoverride/StoredPrefixedStringType.java b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/StoredPrefixedStringType.java index ee32bb28a7..322a777a5b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/typeoverride/StoredPrefixedStringType.java +++ b/hibernate-core/src/test/java/org/hibernate/test/typeoverride/StoredPrefixedStringType.java @@ -23,6 +23,7 @@ */ package org.hibernate.test.typeoverride; +import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -73,6 +74,16 @@ public class StoredPrefixedStringType } return javaTypeDescriptor.wrap( stringValue.substring( PREFIX.length() ), options ); } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) + throws SQLException { + String stringValue = statement.getString( index ); + if ( ! stringValue.startsWith( PREFIX ) ) { + throw new AssertionFailure( "Value read from procedure output param does not have prefix." ); + } + return javaTypeDescriptor.wrap( stringValue.substring( PREFIX.length() ), options ); + } }; } }; diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java index 0925c26268..368ff7b9f3 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/AbstractEntityManagerImpl.java @@ -41,6 +41,7 @@ import javax.persistence.PessimisticLockException; import javax.persistence.PessimisticLockScope; import javax.persistence.Query; import javax.persistence.QueryTimeoutException; +import javax.persistence.StoredProcedureQuery; import javax.persistence.TransactionRequiredException; import javax.persistence.Tuple; import javax.persistence.TupleElement; @@ -81,10 +82,12 @@ import org.hibernate.SQLQuery; import org.hibernate.Session; import org.hibernate.StaleObjectStateException; import org.hibernate.StaleStateException; +import org.hibernate.StoredProcedureCall; import org.hibernate.TransientObjectException; import org.hibernate.TypeMismatchException; import org.hibernate.UnresolvableObjectException; import org.hibernate.cfg.Environment; +import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.dialect.lock.LockingStrategyException; import org.hibernate.dialect.lock.OptimisticEntityLockException; import org.hibernate.dialect.lock.PessimisticEntityLockException; @@ -776,6 +779,38 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage } } + @Override + public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { + throw new NotYetImplementedException(); + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { + try { + StoredProcedureCall call = getSession().createStoredProcedureCall( procedureName ); + return new StoredProcedureQueryImpl( call, this ); + } + catch ( HibernateException he ) { + throw convert( he ); + } + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) { + try { + StoredProcedureCall call = getSession().createStoredProcedureCall( procedureName, resultClasses ); + return new StoredProcedureQueryImpl( call, this ); + } + catch ( HibernateException he ) { + throw convert( he ); + } + } + + @Override + public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) { + throw new NotYetImplementedException(); + } + @SuppressWarnings("unchecked") public T getReference(Class entityClass, Object primaryKey) { try { diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/BaseQueryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/BaseQueryImpl.java new file mode 100644 index 0000000000..d0aed68efd --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/BaseQueryImpl.java @@ -0,0 +1,602 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.ejb; + +import javax.persistence.CacheRetrieveMode; +import javax.persistence.CacheStoreMode; +import javax.persistence.FlushModeType; +import javax.persistence.Parameter; +import javax.persistence.Query; +import javax.persistence.TemporalType; +import javax.persistence.TypedQuery; + +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jboss.logging.Logger; + +import org.hibernate.CacheMode; +import org.hibernate.FlushMode; +import org.hibernate.LockMode; +import org.hibernate.ejb.internal.EntityManagerMessageLogger; +import org.hibernate.ejb.util.CacheModeHelper; +import org.hibernate.ejb.util.ConfigurationHelper; +import org.hibernate.ejb.util.LockModeTypeHelper; + +import static org.hibernate.ejb.QueryHints.HINT_CACHEABLE; +import static org.hibernate.ejb.QueryHints.HINT_CACHE_MODE; +import static org.hibernate.ejb.QueryHints.HINT_CACHE_REGION; +import static org.hibernate.ejb.QueryHints.HINT_COMMENT; +import static org.hibernate.ejb.QueryHints.HINT_FETCH_SIZE; +import static org.hibernate.ejb.QueryHints.HINT_FLUSH_MODE; +import static org.hibernate.ejb.QueryHints.HINT_READONLY; +import static org.hibernate.ejb.QueryHints.HINT_TIMEOUT; +import static org.hibernate.ejb.QueryHints.SPEC_HINT_TIMEOUT; + +/** + * Intended as the base class for all {@link Query} implementations, including {@link TypedQuery} and + * {@link javax.persistence.StoredProcedureQuery}. Care should be taken that all changes here fit with all + * those usages. + * + * @author Steve Ebersole + */ +public abstract class BaseQueryImpl implements Query { + private static final EntityManagerMessageLogger LOG = Logger.getMessageLogger( + EntityManagerMessageLogger.class, + AbstractQueryImpl.class.getName() + ); + + private final HibernateEntityManagerImplementor entityManager; + + private int firstResult; + private int maxResults = -1; + private Map hints; + + + public BaseQueryImpl(HibernateEntityManagerImplementor entityManager) { + this.entityManager = entityManager; + } + + protected HibernateEntityManagerImplementor entityManager() { + return entityManager; + } + + + // Limits (first and max results) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + /** + * Apply the given first-result value. + * + * @param firstResult The specified first-result value. + */ + protected abstract void applyFirstResult(int firstResult); + + @Override + public BaseQueryImpl setFirstResult(int firstResult) { + if ( firstResult < 0 ) { + throw new IllegalArgumentException( + "Negative value (" + firstResult + ") passed to setFirstResult" + ); + } + this.firstResult = firstResult; + applyFirstResult( firstResult ); + return this; + } + + @Override + public int getFirstResult() { + return firstResult; + } + + /** + * Apply the given max results value. + * + * @param maxResults The specified max results + */ + protected abstract void applyMaxResults(int maxResults); + + @Override + public BaseQueryImpl setMaxResults(int maxResult) { + if ( maxResult < 0 ) { + throw new IllegalArgumentException( + "Negative value (" + maxResult + ") passed to setMaxResults" + ); + } + this.maxResults = maxResult; + applyMaxResults( maxResult ); + return this; + } + + public int getSpecifiedMaxResults() { + return maxResults; + } + + @Override + public int getMaxResults() { + return maxResults == -1 + ? Integer.MAX_VALUE // stupid spec... MAX_VALUE?? + : maxResults; + } + + + // Hints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @SuppressWarnings( {"UnusedDeclaration"}) + public Set getSupportedHints() { + return QueryHints.getDefinedHints(); + } + + @Override + public Map getHints() { + return hints; + } + + /** + * Apply the query timeout hint. + * + * @param timeout The timeout (in seconds!) specified as a hint + * + * @return {@code true} if the hint was "applied" + */ + protected abstract boolean applyTimeoutHint(int timeout); + + /** + * Apply the lock timeout (in seconds!) hint + * + * @param timeout The timeout (in seconds!) specified as a hint + * + * @return {@code true} if the hint was "applied" + */ + protected abstract boolean applyLockTimeoutHint(int timeout); + + /** + * Apply the comment hint. + * + * @param comment The comment specified as a hint + * + * @return {@code true} if the hint was "applied" + */ + protected abstract boolean applyCommentHint(String comment); + + /** + * Apply the fetch size hint + * + * @param fetchSize The fetch size specified as a hint + * + * @return {@code true} if the hint was "applied" + */ + protected abstract boolean applyFetchSize(int fetchSize); + + /** + * Apply the cacheable (true/false) hint. + * + * @param isCacheable The value specified as hint + * + * @return {@code true} if the hint was "applied" + */ + protected abstract boolean applyCacheableHint(boolean isCacheable); + + /** + * Apply the cache region hint + * + * @param regionName The name of the cache region specified as a hint + * + * @return {@code true} if the hint was "applied" + */ + protected abstract boolean applyCacheRegionHint(String regionName); + + /** + * Apply the read-only (true/false) hint. + * + * @param isReadOnly The value specified as hint + * + * @return {@code true} if the hint was "applied" + */ + protected abstract boolean applyReadOnlyHint(boolean isReadOnly); + + /** + * Apply the CacheMode hint. + * + * @param cacheMode The CacheMode value specified as a hint. + * + * @return {@code true} if the hint was "applied" + */ + protected abstract boolean applyCacheModeHint(CacheMode cacheMode); + + /** + * Apply the FlushMode hint. + * + * @param flushMode The FlushMode value specified as hint + * + * @return {@code true} if the hint was "applied" + */ + protected abstract boolean applyFlushModeHint(FlushMode flushMode); + + /** + * Can alias-specific lock modes be applied? + * + * @return {@code true} indicates they can be applied, {@code false} otherwise. + */ + protected abstract boolean canApplyLockModesHints(); + + /** + * Apply the alias specific lock modes. Assumes {@link #canApplyLockModesHints()} has already been called and + * returned {@code true}. + * + * @param alias The alias to apply the 'lockMode' to. + * @param lockMode The LockMode to apply. + */ + protected abstract void applyAliasSpecificLockModeHint(String alias, LockMode lockMode); + + @Override + @SuppressWarnings( {"deprecation"}) + public BaseQueryImpl setHint(String hintName, Object value) { + boolean applied = false; + try { + if ( HINT_TIMEOUT.equals( hintName ) ) { + applied = applyTimeoutHint( ConfigurationHelper.getInteger( value ) ); + } + else if ( SPEC_HINT_TIMEOUT.equals( hintName ) ) { + // convert milliseconds to seconds + int timeout = (int)Math.round(ConfigurationHelper.getInteger( value ).doubleValue() / 1000.0 ); + applied = applyTimeoutHint( timeout ); + } + else if ( AvailableSettings.LOCK_TIMEOUT.equals( hintName ) ) { + applied = applyLockTimeoutHint( ConfigurationHelper.getInteger( value ) ); + } + else if ( HINT_COMMENT.equals( hintName ) ) { + applied = applyCommentHint( (String) value ); + } + else if ( HINT_FETCH_SIZE.equals( hintName ) ) { + applied = applyFetchSize( ConfigurationHelper.getInteger( value ) ); + } + else if ( HINT_CACHEABLE.equals( hintName ) ) { + applied = applyCacheableHint( ConfigurationHelper.getBoolean( value ) ); + } + else if ( HINT_CACHE_REGION.equals( hintName ) ) { + applied = applyCacheRegionHint( (String) value ); + } + else if ( HINT_READONLY.equals( hintName ) ) { + applied = applyReadOnlyHint( ConfigurationHelper.getBoolean( value ) ); + } + else if ( HINT_CACHE_MODE.equals( hintName ) ) { + applied = applyCacheModeHint( ConfigurationHelper.getCacheMode( value ) ); + } + else if ( HINT_FLUSH_MODE.equals( hintName ) ) { + applied = applyFlushModeHint( ConfigurationHelper.getFlushMode( value ) ); + } + else if ( AvailableSettings.SHARED_CACHE_RETRIEVE_MODE.equals( hintName ) ) { + final CacheRetrieveMode retrieveMode = (CacheRetrieveMode) value; + + CacheStoreMode storeMode = hints != null + ? (CacheStoreMode) hints.get( AvailableSettings.SHARED_CACHE_STORE_MODE ) + : null; + if ( storeMode == null ) { + storeMode = (CacheStoreMode) entityManager.getProperties().get( AvailableSettings.SHARED_CACHE_STORE_MODE ); + } + applied = applyCacheModeHint( CacheModeHelper.interpretCacheMode( storeMode, retrieveMode ) ); + } + else if ( AvailableSettings.SHARED_CACHE_STORE_MODE.equals( hintName ) ) { + final CacheStoreMode storeMode = (CacheStoreMode) value; + + CacheRetrieveMode retrieveMode = hints != null + ? (CacheRetrieveMode) hints.get( AvailableSettings.SHARED_CACHE_RETRIEVE_MODE ) + : null; + if ( retrieveMode == null ) { + retrieveMode = (CacheRetrieveMode) entityManager.getProperties().get( AvailableSettings.SHARED_CACHE_RETRIEVE_MODE ); + } + applied = applyCacheModeHint( + CacheModeHelper.interpretCacheMode( storeMode, retrieveMode ) + ); + } + else if ( hintName.startsWith( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE ) ) { + if ( canApplyLockModesHints() ) { + // extract the alias + final String alias = hintName.substring( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE.length() + 1 ); + // determine the LockMode + try { + final LockMode lockMode = LockModeTypeHelper.interpretLockMode( value ); + applyAliasSpecificLockModeHint( alias, lockMode ); + } + catch ( Exception e ) { + LOG.unableToDetermineLockModeValue( hintName, value ); + applied = false; + } + } + else { + applied = false; + } + } + else { + LOG.ignoringUnrecognizedQueryHint( hintName ); + } + } + catch ( ClassCastException e ) { + throw new IllegalArgumentException( "Value for hint" ); + } + + if ( applied ) { + if ( hints == null ) { + hints = new HashMap(); + } + hints.put( hintName, value ); + } + else { + LOG.debugf( "Skipping unsupported query hint [%s]", hintName ); + } + + return this; + } + + + // FlushMode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private FlushModeType jpaFlushMode; + + @Override + public BaseQueryImpl setFlushMode(FlushModeType jpaFlushMode) { + this.jpaFlushMode = jpaFlushMode; + // TODO : treat as hint? + if ( jpaFlushMode == FlushModeType.AUTO ) { + applyFlushModeHint( FlushMode.AUTO ); + } + else if ( jpaFlushMode == FlushModeType.COMMIT ) { + applyFlushModeHint( FlushMode.COMMIT ); + } + return this; + } + + @SuppressWarnings( {"UnusedDeclaration"}) + protected FlushModeType getSpecifiedFlushMode() { + return jpaFlushMode; + } + + @Override + public FlushModeType getFlushMode() { + return jpaFlushMode != null + ? jpaFlushMode + : entityManager.getFlushMode(); + } + + + // Parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private List parameters; + + /** + * Hibernate specific extension to the JPA {@link Parameter} contract. + */ + protected static interface ParameterImplementor extends Parameter { + public boolean isBindable(); + + public ParameterValue getBoundValue(); + } + + protected static class ParameterValue { + private final Object value; + private final TemporalType specifiedTemporalType; + + public ParameterValue(Object value, TemporalType specifiedTemporalType) { + this.value = value; + this.specifiedTemporalType = specifiedTemporalType; + } + + public Object getValue() { + return value; + } + + public TemporalType getSpecifiedTemporalType() { + return specifiedTemporalType; + } + } + + private Map,ParameterValue> parameterBindingMap; + + private Map,ParameterValue> parameterBindingMap() { + if ( parameterBindingMap == null ) { + parameterBindingMap = new HashMap, ParameterValue>(); + } + return parameterBindingMap; + } + + protected void registerParameter(ParameterImplementor parameter) { + if ( parameter == null ) { + throw new IllegalArgumentException( "parameter cannot be null" ); + } + + if ( parameterBindingMap().containsKey( parameter ) ) { + return; + } + + parameterBindingMap().put( parameter, null ); + } + + @SuppressWarnings("unchecked") + protected void registerParameterBinding(Parameter parameter, ParameterValue bindValue) { + validateParameterBinding( (ParameterImplementor) parameter, bindValue ); + parameterBindingMap().put( (ParameterImplementor) parameter, bindValue ); + } + + protected void validateParameterBinding(ParameterImplementor parameter, ParameterValue bindValue) { + if ( parameter == null ) { + throw new IllegalArgumentException( "parameter cannot be null" ); + } + + if ( ! parameter.isBindable() ) { + throw new IllegalArgumentException( "Parameter [" + parameter + "] not valid for binding" ); + } + + if ( ! parameterBindingMap().containsKey( parameter ) ) { + throw new IllegalArgumentException( "Unknown parameter [" + parameter + "] specified for value binding" ); + } + + if ( isBound( parameter ) ) { + throw new IllegalArgumentException( "Parameter [" + parameter + "] already had bound value" ); + } + + validateParameterBindingTypes( parameter, bindValue ); + } + + protected abstract void validateParameterBindingTypes(ParameterImplementor parameter, ParameterValue bindValue); + + protected ParameterValue makeBindValue(Object value) { + return new ParameterValue( value, null ); + } + + protected ParameterValue makeBindValue(Calendar value, TemporalType temporalType) { + return new ParameterValue( value, temporalType ); + } + + protected ParameterValue makeBindValue(Date value, TemporalType temporalType) { + return new ParameterValue( value, temporalType ); + } + + @Override + public BaseQueryImpl setParameter(Parameter param, T value) { + registerParameterBinding( param, makeBindValue( value ) ); + return this; + } + + @Override + public BaseQueryImpl setParameter(Parameter param, Calendar value, TemporalType temporalType) { + registerParameterBinding( param, makeBindValue( value, temporalType ) ); + return this; + } + + @Override + public BaseQueryImpl setParameter(Parameter param, Date value, TemporalType temporalType) { + registerParameterBinding( param, makeBindValue( value, temporalType ) ); + return this; + } + + @Override + public BaseQueryImpl setParameter(String name, Object value) { + registerParameterBinding( getParameter( name ), makeBindValue( value ) ); + return this; + } + + @Override + public BaseQueryImpl setParameter(String name, Calendar value, TemporalType temporalType) { + registerParameterBinding( getParameter( name ), makeBindValue( value, temporalType ) ); + return this; + } + + @Override + public BaseQueryImpl setParameter(String name, Date value, TemporalType temporalType) { + registerParameterBinding( getParameter( name ), makeBindValue( value, temporalType ) ); + return this; + } + + @Override + public BaseQueryImpl setParameter(int position, Object value) { + registerParameterBinding( getParameter( position ), makeBindValue( value ) ); + return this; + } + + @Override + public BaseQueryImpl setParameter(int position, Calendar value, TemporalType temporalType) { + registerParameterBinding( getParameter( position ), makeBindValue( value, temporalType ) ); + return this; + } + + @Override + public BaseQueryImpl setParameter(int position, Date value, TemporalType temporalType) { + registerParameterBinding( getParameter( position ), makeBindValue( value, temporalType ) ); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Set> getParameters() { + return (Set>) parameterBindingMap().keySet(); + } + + @Override + public Parameter getParameter(String name) { + if ( parameterBindingMap() != null ) { + for ( ParameterImplementor param : parameterBindingMap.keySet() ) { + if ( name.equals( param.getName() ) ) { + return param; + } + } + } + throw new IllegalArgumentException( "Parameter with that name [" + name + "] did not exist" ); + } + + @Override + @SuppressWarnings("unchecked") + public Parameter getParameter(String name, Class type) { + return (Parameter) getParameter( name ); + } + + @Override + public Parameter getParameter(int position) { + if ( parameterBindingMap() != null ) { + for ( ParameterImplementor param : parameterBindingMap.keySet() ) { + if ( position == param.getPosition() ) { + return param; + } + } + } + throw new IllegalArgumentException( "Parameter with that position [" + position + "] did not exist" ); + } + + @Override + @SuppressWarnings("unchecked") + public Parameter getParameter(int position, Class type) { + return (Parameter) getParameter( position ); + } + + @Override + public boolean isBound(Parameter param) { + return parameterBindingMap() != null + && parameterBindingMap.get( (ParameterImplementor) param ) != null; + } + + @Override + @SuppressWarnings("unchecked") + public T getParameterValue(Parameter param) { + if ( parameterBindingMap != null ) { + final ParameterValue boundValue = parameterBindingMap.get( (ParameterImplementor) param ); + if ( boundValue != null ) { + return (T) boundValue.getValue(); + } + } + throw new IllegalStateException( "Parameter [" + param + "] has not yet been bound" ); + } + + @Override + public Object getParameterValue(String name) { + return getParameterValue( getParameter( name ) ); + } + + @Override + public Object getParameterValue(int position) { + return getParameterValue( getParameter( position ) ); + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/EntityManagerFactoryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/EntityManagerFactoryImpl.java index 86e5573e6f..0525efa579 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/EntityManagerFactoryImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/EntityManagerFactoryImpl.java @@ -1,8 +1,10 @@ /* - * Copyright (c) 2009, Red Hat Middleware LLC or third-party contributors as + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, 2012, 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 @@ -48,7 +50,6 @@ import org.hibernate.Hibernate; import org.hibernate.SessionFactory; import org.hibernate.cache.spi.RegionFactory; import org.hibernate.cfg.Configuration; -import org.hibernate.cfg.NotYetImplementedException; import org.hibernate.ejb.criteria.CriteriaBuilderImpl; import org.hibernate.ejb.internal.EntityManagerFactoryRegistry; import org.hibernate.ejb.metamodel.MetamodelImpl; diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/EntityManagerImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/EntityManagerImpl.java index d34d476d6b..05ba76dd08 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/EntityManagerImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/EntityManagerImpl.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2009-2011, Red Hat Inc. or third-party contributors as + * Copyright (c) 2009, 2011, 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. @@ -23,24 +23,21 @@ */ package org.hibernate.ejb; -import java.util.Map; import javax.persistence.PersistenceContextType; import javax.persistence.PersistenceException; -import javax.persistence.Query; -import javax.persistence.StoredProcedureQuery; import javax.persistence.spi.PersistenceUnitTransactionType; +import java.util.Map; import org.jboss.logging.Logger; import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.Session; -import org.hibernate.cfg.NotYetImplementedException; -import org.hibernate.engine.spi.SessionOwner; import org.hibernate.annotations.common.util.ReflectHelper; import org.hibernate.ejb.internal.EntityManagerMessageLogger; import org.hibernate.engine.spi.SessionBuilderImplementor; import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.SessionOwner; /** * Hibernate implementation of {@link javax.persistence.EntityManager}. @@ -129,26 +126,6 @@ public class EntityManagerImpl extends AbstractEntityManagerImpl implements Sess return session; } - @Override - public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { - throw new NotYetImplementedException(); - } - - @Override - public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { - throw new NotYetImplementedException(); - } - - @Override - public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) { - throw new NotYetImplementedException(); - } - - @Override - public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) { - throw new NotYetImplementedException(); - } - public void close() { checkEntityManagerFactory(); if ( !open ) { diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/ejb/StoredProcedureQueryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/StoredProcedureQueryImpl.java new file mode 100644 index 0000000000..c1c3e4f96b --- /dev/null +++ b/hibernate-entitymanager/src/main/java/org/hibernate/ejb/StoredProcedureQueryImpl.java @@ -0,0 +1,283 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.ejb; + +import javax.persistence.FlushModeType; +import javax.persistence.LockModeType; +import javax.persistence.Parameter; +import javax.persistence.ParameterMode; +import javax.persistence.Query; +import javax.persistence.StoredProcedureQuery; +import javax.persistence.TemporalType; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import org.hibernate.CacheMode; +import org.hibernate.FlushMode; +import org.hibernate.LockMode; +import org.hibernate.StoredProcedureCall; +import org.hibernate.StoredProcedureOutputs; +import org.hibernate.StoredProcedureResultSetReturn; +import org.hibernate.StoredProcedureReturn; +import org.hibernate.StoredProcedureUpdateCountReturn; + +/** + * @author Steve Ebersole + */ +public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredProcedureQuery { + private final StoredProcedureCall storedProcedureCall; + private StoredProcedureOutputs storedProcedureOutputs; + + public StoredProcedureQueryImpl(StoredProcedureCall storedProcedureCall, HibernateEntityManagerImplementor entityManager) { + super( entityManager ); + this.storedProcedureCall = storedProcedureCall; + } + + @Override + protected boolean applyTimeoutHint(int timeout) { + storedProcedureCall.setTimeout( timeout ); + return true; + } + + @Override + protected boolean applyCacheableHint(boolean isCacheable) { + storedProcedureCall.setCacheable( isCacheable ); + return true; + } + + @Override + protected boolean applyCacheRegionHint(String regionName) { + storedProcedureCall.setCacheRegion( regionName ); + return true; + } + + @Override + protected boolean applyReadOnlyHint(boolean isReadOnly) { + storedProcedureCall.setReadOnly( isReadOnly ); + return true; + } + + @Override + protected boolean applyCacheModeHint(CacheMode cacheMode) { + storedProcedureCall.setCacheMode( cacheMode ); + return true; + } + + @Override + protected boolean applyFlushModeHint(FlushMode flushMode) { + storedProcedureCall.setFlushMode( flushMode ); + return true; + } + + @Override + @SuppressWarnings("unchecked") + public StoredProcedureQuery registerStoredProcedureParameter(int position, Class type, ParameterMode mode) { + storedProcedureCall.registerStoredProcedureParameter( position, type, mode ); + return this; + } + + @Override + public StoredProcedureQuery registerStoredProcedureParameter(String parameterName, Class type, ParameterMode mode) { + storedProcedureCall.registerStoredProcedureParameter( parameterName, type, mode ); + return this; + } + + @Override + protected void validateParameterBindingTypes(ParameterImplementor parameter, ParameterValue bindValue) { + } + + + // covariant returns ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public StoredProcedureQueryImpl setFlushMode(FlushModeType jpaFlushMode) { + return (StoredProcedureQueryImpl) super.setFlushMode( jpaFlushMode ); + } + + @Override + public StoredProcedureQueryImpl setHint(String hintName, Object value) { + return (StoredProcedureQueryImpl) super.setHint( hintName, value ); + } + + @Override + public StoredProcedureQueryImpl setParameter(Parameter param, T value) { + return (StoredProcedureQueryImpl) super.setParameter( param, value ); + } + + @Override + public StoredProcedureQueryImpl setParameter(Parameter param, Calendar value, TemporalType temporalType) { + return (StoredProcedureQueryImpl) super.setParameter( param, value, temporalType ); + } + + @Override + public StoredProcedureQueryImpl setParameter(Parameter param, Date value, TemporalType temporalType) { + return (StoredProcedureQueryImpl) super.setParameter( param, value, temporalType ); + } + + @Override + public StoredProcedureQueryImpl setParameter(String name, Object value) { + return ( StoredProcedureQueryImpl) super.setParameter( name, value ); + } + + @Override + public StoredProcedureQueryImpl setParameter(String name, Calendar value, TemporalType temporalType) { + return (StoredProcedureQueryImpl) super.setParameter( name, value, temporalType ); + } + + @Override + public StoredProcedureQueryImpl setParameter(String name, Date value, TemporalType temporalType) { + return (StoredProcedureQueryImpl) super.setParameter( name, value, temporalType ); + } + + @Override + public StoredProcedureQueryImpl setParameter(int position, Object value) { + return (StoredProcedureQueryImpl) super.setParameter( position, value ); + } + + @Override + public StoredProcedureQueryImpl setParameter(int position, Calendar value, TemporalType temporalType) { + return (StoredProcedureQueryImpl) super.setParameter( position, value, temporalType ); + } + + @Override + public StoredProcedureQueryImpl setParameter(int position, Date value, TemporalType temporalType) { + return (StoredProcedureQueryImpl) super.setParameter( position, value, temporalType ); + } + + + // outputs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + private StoredProcedureOutputs outputs() { + if ( storedProcedureOutputs == null ) { + storedProcedureOutputs = storedProcedureCall.getOutputs(); + } + return storedProcedureOutputs; + } + + @Override + public Object getOutputParameterValue(int position) { + return outputs().getOutputParameterValue( position ); + } + + @Override + public Object getOutputParameterValue(String parameterName) { + return outputs().getOutputParameterValue( parameterName ); + } + + @Override + public boolean execute() { + return outputs().hasMoreReturns(); + } + + @Override + public int executeUpdate() { + return getUpdateCount(); + } + + @Override + public boolean hasMoreResults() { + return outputs().hasMoreReturns(); + } + + @Override + public int getUpdateCount() { + final StoredProcedureReturn nextReturn = outputs().getNextReturn(); + if ( nextReturn.isResultSet() ) { + return -1; + } + return ( (StoredProcedureUpdateCountReturn) nextReturn ).getUpdateCount(); + } + + @Override + public List getResultList() { + final StoredProcedureReturn nextReturn = outputs().getNextReturn(); + if ( ! nextReturn.isResultSet() ) { + return null; // todo : what should be thrown/returned here? + } + return ( (StoredProcedureResultSetReturn) nextReturn ).getResultList(); + } + + @Override + public Object getSingleResult() { + final StoredProcedureReturn nextReturn = outputs().getNextReturn(); + if ( ! nextReturn.isResultSet() ) { + return null; // todo : what should be thrown/returned here? + } + return ( (StoredProcedureResultSetReturn) nextReturn ).getSingleResult(); + } + + @Override + public T unwrap(Class cls) { + return null; + } + + + // ugh ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + public Query setLockMode(LockModeType lockMode) { + return null; + } + + @Override + public LockModeType getLockMode() { + return null; + } + + + // unsupported hints/calls ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + @Override + protected void applyFirstResult(int firstResult) { + } + + @Override + protected void applyMaxResults(int maxResults) { + } + + @Override + protected boolean canApplyLockModesHints() { + return false; + } + + @Override + protected void applyAliasSpecificLockModeHint(String alias, LockMode lockMode) { + } + + @Override + protected boolean applyLockTimeoutHint(int timeout) { + return false; + } + + @Override + protected boolean applyCommentHint(String comment) { + return false; + } + + @Override + protected boolean applyFetchSize(int fetchSize) { + return false; + } +}