Author: max...@jb... Date: 2006-05-05 16:29:41 -0400 (Fri, 05 May 2006) New Revision: 9896 Modified: trunk/Hibernate3/src/org/hibernate/action/BulkOperationCleanupAction.java trunk/Hibernate3/src/org/hibernate/engine/SessionImplementor.java trunk/Hibernate3/src/org/hibernate/engine/query/NativeSQLQueryPlan.java trunk/Hibernate3/src/org/hibernate/impl/AbstractQueryImpl.java trunk/Hibernate3/src/org/hibernate/impl/SQLQueryImpl.java trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java trunk/Hibernate3/src/org/hibernate/impl/StatelessSessionImpl.java trunk/Hibernate3/test/org/hibernate/test/hql/BulkManipulationTest.java trunk/Hibernate3/test/org/hibernate/test/hql/Vehicle.hbm.xml Log: HHH-870 support SQL updates in named queries Modified: trunk/Hibernate3/src/org/hibernate/action/BulkOperationCleanupAction.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/action/BulkOperationCleanupAction.java 2006-05-05 19:27:17 UTC (rev 9895) +++ trunk/Hibernate3/src/org/hibernate/action/BulkOperationCleanupAction.java 2006-05-05 20:29:41 UTC (rev 9896) @@ -2,10 +2,15 @@ package org.hibernate.action; import org.hibernate.HibernateException; +import org.hibernate.metadata.ClassMetadata; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Queryable; +import org.hibernate.engine.SessionFactoryImplementor; import org.hibernate.engine.SessionImplementor; import java.io.Serializable; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.Iterator; import java.util.HashSet; @@ -45,7 +50,55 @@ this.spaces[i] = ( Serializable ) tmpSpaces.get( i ); } } + + /** Create an action that will evict collection and entity regions based on queryspaces (table names). + * TODO: cache the autodetected information and pass it in instead. + **/ + public BulkOperationCleanupAction(SessionImplementor session, Set querySpaces) { + this.session = session; + if(querySpaces!=null && !querySpaces.isEmpty()) { + Set tmpSpaces = new HashSet(querySpaces); + SessionFactoryImplementor factory = session.getFactory(); + Iterator iterator = factory.getAllClassMetadata().entrySet().iterator(); + while ( iterator.hasNext() ) { + Map.Entry entry = (Map.Entry) iterator.next(); + String entityName = (String) entry.getKey(); + EntityPersister persister = factory.getEntityPersister( entityName ); + Serializable[] entitySpaces = persister.getQuerySpaces(); + + if (affectedEntity( querySpaces, entitySpaces )) { + if ( persister.hasCache() ) { + affectedEntityNames.add( persister.getEntityName() ); + } + Set roles = session.getFactory().getCollectionRolesByEntityParticipant( persister.getEntityName() ); + if ( roles != null ) { + affectedCollectionRoles.addAll( roles ); + } + for ( int y = 0; y < entitySpaces.length; y++ ) { + tmpSpaces.add( entitySpaces[y] ); + } + } + + } + + this.spaces = (Serializable[]) tmpSpaces.toArray( new Serializable[tmpSpaces.size()] ); + } else { + // TODO: no synchronize info, sync on *all* ? + this.spaces = new Serializable[0]; + } + } + + + private boolean affectedEntity(Set querySpaces, Serializable[] entitySpaces) { + for ( int i = 0; i < entitySpaces.length; i++ ) { + if ( querySpaces.contains( entitySpaces[i] ) ) { + return true; + } + } + return false; + } + public void init() { evictEntityRegions(); evictCollectionRegions(); @@ -69,7 +122,7 @@ } public void execute() throws HibernateException { - // nothing to do + // nothing to do } private void evictEntityRegions() { Modified: trunk/Hibernate3/src/org/hibernate/engine/SessionImplementor.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/engine/SessionImplementor.java 2006-05-05 19:27:17 UTC (rev 9895) +++ trunk/Hibernate3/src/org/hibernate/engine/SessionImplementor.java 2006-05-05 20:29:41 UTC (rev 9896) @@ -254,6 +254,11 @@ */ int executeUpdate(String query, QueryParameters queryParameters) throws HibernateException; + /** + * Execute a native SQL update or delete query + */ + int executeNativeUpdate(NativeSQLQuerySpecification specification, QueryParameters queryParameters) throws HibernateException; + // copied from Session: public EntityMode getEntityMode(); Modified: trunk/Hibernate3/src/org/hibernate/engine/query/NativeSQLQueryPlan.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/engine/query/NativeSQLQueryPlan.java 2006-05-05 19:27:17 UTC (rev 9895) +++ trunk/Hibernate3/src/org/hibernate/engine/query/NativeSQLQueryPlan.java 2006-05-05 20:29:41 UTC (rev 9896) @@ -1,30 +1,49 @@ package org.hibernate.engine.query; +import java.io.Serializable; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.HibernateException; +import org.hibernate.QueryException; +import org.hibernate.action.BulkOperationCleanupAction; +import org.hibernate.engine.QueryParameters; +import org.hibernate.engine.SessionFactoryImplementor; +import org.hibernate.engine.SessionImplementor; +import org.hibernate.engine.TypedValue; +import org.hibernate.event.EventSource; +import org.hibernate.exception.JDBCExceptionHelper; import org.hibernate.loader.custom.SQLCustomQuery; -import org.hibernate.engine.SessionFactoryImplementor; -import org.hibernate.engine.NamedSQLQueryDefinition; +import org.hibernate.pretty.Formatter; +import org.hibernate.type.Type; +import org.hibernate.util.ArrayHelper; -import java.io.Serializable; - /** * Defines a query execution plan for a native-SQL query. - * + * * @author <a href="mailto:st...@hi...">Steve Ebersole </a> */ public class NativeSQLQueryPlan implements Serializable { private final String sourceQuery; + private final SQLCustomQuery customQuery; - public NativeSQLQueryPlan(NativeSQLQuerySpecification specification, SessionFactoryImplementor factory) { + private static final Log log = LogFactory.getLog(NativeSQLQueryPlan.class); + + public NativeSQLQueryPlan(NativeSQLQuerySpecification specification, + SessionFactoryImplementor factory) { this.sourceQuery = specification.getQueryString(); - customQuery = new SQLCustomQuery( - specification.getSqlQueryReturns(), - specification.getSqlQueryScalarReturns(), - specification.getQueryString(), - specification.getQuerySpaces(), - factory - ); + customQuery = new SQLCustomQuery( specification.getSqlQueryReturns(), + specification.getSqlQueryScalarReturns(), specification + .getQueryString(), specification.getQuerySpaces(), + factory ); } public String getSourceQuery() { @@ -34,4 +53,129 @@ public SQLCustomQuery getCustomQuery() { return customQuery; } + + private int[] getNamedParameterLocs(String name) throws QueryException { + Object loc = customQuery.getNamedParameterBindPoints().get( name ); + if ( loc == null ) { + throw new QueryException( + "Named parameter does not appear in Query: " + name, + customQuery.getSQL() ); + } + if ( loc instanceof Integer ) { + return new int[] { ((Integer) loc ).intValue() }; + } + else { + return ArrayHelper.toIntArray( (List) loc ); + } + } + + /** + * Bind positional parameter values to the <tt>PreparedStatement</tt> + * (these are parameters specified by a JDBC-style ?). + */ + private int bindPositionalParameters(final PreparedStatement st, + final QueryParameters queryParameters, final int start, + final SessionImplementor session) throws SQLException, + HibernateException { + + final Object[] values = queryParameters + .getFilteredPositionalParameterValues(); + final Type[] types = queryParameters + .getFilteredPositionalParameterTypes(); + int span = 0; + for (int i = 0; i < values.length; i++) { + types[i].nullSafeSet( st, values[i], start + span, session ); + span += types[i].getColumnSpan( session.getFactory() ); + } + return span; + } + + /** + * Bind named parameters to the <tt>PreparedStatement</tt>. This has an + * empty implementation on this superclass and should be implemented by + * subclasses (queries) which allow named parameters. + */ + private int bindNamedParameters(final PreparedStatement ps, + final Map namedParams, final int start, + final SessionImplementor session) throws SQLException, + HibernateException { + + if ( namedParams != null ) { + // assumes that types are all of span 1 + Iterator iter = namedParams.entrySet().iterator(); + int result = 0; + while ( iter.hasNext() ) { + Map.Entry e = (Map.Entry) iter.next(); + String name = (String) e.getKey(); + TypedValue typedval = (TypedValue) e.getValue(); + int[] locs = getNamedParameterLocs( name ); + for (int i = 0; i < locs.length; i++) { + if ( log.isDebugEnabled() ) { + log.debug( "bindNamedParameters() " + + typedval.getValue() + " -> " + name + " [" + + (locs[i] + start ) + "]" ); + } + typedval.getType().nullSafeSet( ps, typedval.getValue(), + locs[i] + start, session ); + } + result += locs.length; + } + return result; + } + else { + return 0; + } + } + + protected void coordinateSharedCacheCleanup(SessionImplementor session) { + BulkOperationCleanupAction action = new BulkOperationCleanupAction( session, getCustomQuery().getQuerySpaces() ); + + action.init(); + + if ( session.isEventSource() ) { + ( ( EventSource ) session ).getActionQueue().addAction( action ); + } + } + + public int performExecuteUpdate(QueryParameters queryParameters, + SessionImplementor session) throws HibernateException { + + coordinateSharedCacheCleanup( session ); + + if(queryParameters.isCallable()) { + throw new IllegalArgumentException("callable not yet supported for native queries"); + } + + int result = 0; + PreparedStatement ps = null; + try { + queryParameters.processFilters( this.customQuery.getSQL(), + session ); + String sql = queryParameters.getFilteredSQL(); + + ps = session.getBatcher().prepareStatement( sql ); + + try { + int col = 1; + col += bindPositionalParameters( ps, queryParameters, col, + session ); + col += bindNamedParameters( ps, queryParameters + .getNamedParameters(), col, session ); + result = ps.executeUpdate(); + } + finally { + if ( ps != null ) { + session.getBatcher().closeStatement( ps ); + } + } + } + catch (SQLException sqle) { + throw JDBCExceptionHelper.convert( session.getFactory() + .getSQLExceptionConverter(), sqle, + "could not execute native bulk manipulation query", this.sourceQuery ); + } + + return result; + } + } Modified: trunk/Hibernate3/src/org/hibernate/impl/AbstractQueryImpl.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/impl/AbstractQueryImpl.java 2006-05-05 19:27:17 UTC (rev 9895) +++ trunk/Hibernate3/src/org/hibernate/impl/AbstractQueryImpl.java 2006-05-05 20:29:41 UTC (rev 9896) @@ -756,10 +756,6 @@ return uniqueElement( list() ); } - public int executeUpdate() throws HibernateException { - throw new UnsupportedOperationException( "Update queries only supported through HQL" ); - } - static Object uniqueElement(List list) throws NonUniqueResultException { int size = list.size(); if (size==0) return null; Modified: trunk/Hibernate3/src/org/hibernate/impl/SQLQueryImpl.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/impl/SQLQueryImpl.java 2006-05-05 19:27:17 UTC (rev 9895) +++ trunk/Hibernate3/src/org/hibernate/impl/SQLQueryImpl.java 2006-05-05 20:29:41 UTC (rev 9896) @@ -317,4 +317,10 @@ } return this; } + + public int executeUpdate() throws HibernateException { + Map namedParams = getNamedParams(); + return getSession().executeNativeUpdate(generateQuerySpecification( namedParams ), getQueryParameters( namedParams )); + } + } Modified: trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java 2006-05-05 19:27:17 UTC (rev 9895) +++ trunk/Hibernate3/src/org/hibernate/impl/SessionImpl.java 2006-05-05 20:29:41 UTC (rev 9896) @@ -29,7 +29,6 @@ import org.hibernate.LockMode; import org.hibernate.MappingException; import org.hibernate.ObjectDeletedException; -import org.hibernate.ObjectNotFoundException; import org.hibernate.Query; import org.hibernate.QueryException; import org.hibernate.ReplicationMode; @@ -54,6 +53,8 @@ import org.hibernate.engine.Status; import org.hibernate.engine.query.FilterQueryPlan; import org.hibernate.engine.query.HQLQueryPlan; +import org.hibernate.engine.query.NativeSQLQueryPlan; +import org.hibernate.engine.query.NativeSQLQuerySpecification; import org.hibernate.event.AutoFlushEvent; import org.hibernate.event.AutoFlushEventListener; import org.hibernate.event.DeleteEvent; @@ -88,6 +89,8 @@ import org.hibernate.loader.criteria.CriteriaLoader; import org.hibernate.loader.custom.CustomLoader; import org.hibernate.loader.custom.CustomQuery; +import org.hibernate.loader.custom.SQLQueryReturn; +import org.hibernate.loader.custom.SQLQueryScalarReturn; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.OuterJoinLoadable; @@ -116,7 +119,7 @@ // todo : need to find a clean way to handle the "event source" role // a seperate classs responsible for generating/dispatching events just duplicates most of the Session methods... - // passing around seperate references to interceptor, factory, actionQueue, and persistentContext is not manageable... + // passing around seperate reto interceptor, factory, actionQueue, and persistentContext is not manageable... private static final Log log = LogFactory.getLog(SessionImpl.class); @@ -1137,6 +1140,27 @@ return result; } + public int executeNativeUpdate(NativeSQLQuerySpecification nativeQuerySpecification, + QueryParameters queryParameters) throws HibernateException { + errorIfClosed(); + checkTransactionSynchStatus(); + queryParameters.validateParameters(); + NativeSQLQueryPlan plan = getNativeSQLQueryPlan(nativeQuerySpecification); + + + autoFlushIfRequired( plan.getCustomQuery().getQuerySpaces() ); + + boolean success = false; + int result = 0; + try { + result = plan.performExecuteUpdate(queryParameters, this); + success = true; + } finally { + afterOperation(success); + } + return result; + } + public Iterator iterate(String query) throws HibernateException { return iterate( query, new QueryParameters() ); } Modified: trunk/Hibernate3/src/org/hibernate/impl/StatelessSessionImpl.java =================================================================== --- trunk/Hibernate3/src/org/hibernate/impl/StatelessSessionImpl.java 2006-05-05 19:27:17 UTC (rev 9895) +++ trunk/Hibernate3/src/org/hibernate/impl/StatelessSessionImpl.java 2006-05-05 20:29:41 UTC (rev 9896) @@ -9,6 +9,8 @@ import java.util.Map; import java.util.Set; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.hibernate.CacheMode; import org.hibernate.ConnectionReleaseMode; import org.hibernate.Criteria; @@ -26,7 +28,6 @@ import org.hibernate.Transaction; import org.hibernate.UnresolvableObjectException; import org.hibernate.cache.CacheKey; -import org.hibernate.pretty.MessageHelper; import org.hibernate.collection.PersistentCollection; import org.hibernate.engine.EntityKey; import org.hibernate.engine.PersistenceContext; @@ -34,6 +35,8 @@ import org.hibernate.engine.StatefulPersistenceContext; import org.hibernate.engine.Versioning; import org.hibernate.engine.query.HQLQueryPlan; +import org.hibernate.engine.query.NativeSQLQueryPlan; +import org.hibernate.engine.query.NativeSQLQuerySpecification; import org.hibernate.event.EventListeners; import org.hibernate.id.IdentifierGeneratorFactory; import org.hibernate.jdbc.Batcher; @@ -43,11 +46,10 @@ import org.hibernate.loader.custom.CustomQuery; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.OuterJoinLoadable; +import org.hibernate.pretty.MessageHelper; import org.hibernate.proxy.HibernateProxy; import org.hibernate.type.Type; import org.hibernate.util.CollectionHelper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** * @author Gavin King @@ -606,4 +608,23 @@ // no auto-flushing to support in stateless session return false; } + + public int executeNativeUpdate(NativeSQLQuerySpecification nativeSQLQuerySpecification, + QueryParameters queryParameters) throws HibernateException { + errorIfClosed(); + queryParameters.validateParameters(); + NativeSQLQueryPlan plan = getNativeSQLQueryPlan(nativeSQLQuerySpecification); + + boolean success = false; + int result = 0; + try { + result = plan.performExecuteUpdate(queryParameters, this); + success = true; + } finally { + afterOperation(success); + } + temporaryPersistenceContext.clear(); + return result; + } + } Modified: trunk/Hibernate3/test/org/hibernate/test/hql/BulkManipulationTest.java =================================================================== --- trunk/Hibernate3/test/org/hibernate/test/hql/BulkManipulationTest.java 2006-05-05 19:27:17 UTC (rev 9895) +++ trunk/Hibernate3/test/org/hibernate/test/hql/BulkManipulationTest.java 2006-05-05 20:29:41 UTC (rev 9896) @@ -138,35 +138,43 @@ TestData data = new TestData(); data.prepare(); - try { - Session s = openSession(); - Transaction t = s.beginTransaction(); - - List l = s.createQuery("from Vehicle").list(); - assertEquals(l.size(),4); - - s.createSQLQuery( "insert into Pickup (id, vin, owner) select id, vin, owner from Car" ).executeUpdate(); - - l = s.createQuery("from Vehicle").list(); - assertEquals(l.size(),5); - - t.commit(); - t = s.beginTransaction(); - - s.createSQLQuery( "delete Vehicle" ).executeUpdate(); - - l = s.createQuery("from Vehicle").list(); - assertEquals(l.size(),0); - - t.commit(); - s.close(); - fail("native sql bulk manipulation should fail with UnsupportedOperationException"); - } catch(UnsupportedOperationException eo) { - // should happen - } + Session s = openSession(); + Transaction t = s.beginTransaction(); + + List l = s.createQuery("from Vehicle").list(); + assertEquals(l.size(),4); + + s.createSQLQuery( "insert into PICKUP (id, vin, owner) select id, vin, owner from Car" ).executeUpdate(); + + l = s.createQuery("from Vehicle").list(); + assertEquals(l.size(),5); + + t.commit(); + t = s.beginTransaction(); + + s.createSQLQuery( "delete from TRUCK" ).executeUpdate(); + + l = s.createQuery("from Vehicle").list(); + assertEquals(l.size(),4); + + Car c = (Car) s.createQuery( "from Car where owner = 'Kirsten'" ).uniqueResult(); + c.setOwner("NotKirsten"); + assertEquals(0,s.getNamedQuery( "native-delete-car" ).setString( 0, "Kirsten" ).executeUpdate()); + assertEquals(1,s.getNamedQuery( "native-delete-car" ).setString( 0, "NotKirsten" ).executeUpdate()); + assertEquals(0,s.createSQLQuery( "delete from SUV where owner = :owner" ).setString( "owner", "NotThere" ).executeUpdate()); + assertEquals(1,s.createSQLQuery( "delete from SUV where owner = :owner" ).setString( "owner", "Joe" ).executeUpdate()); + s.createSQLQuery( "delete from PICKUP" ).executeUpdate(); + l = s.createQuery("from Vehicle").list(); + assertEquals(l.size(),0); + + + t.commit(); + s.close(); + + data.cleanup(); } Modified: trunk/Hibernate3/test/org/hibernate/test/hql/Vehicle.hbm.xml =================================================================== --- trunk/Hibernate3/test/org/hibernate/test/hql/Vehicle.hbm.xml 2006-05-05 19:27:17 UTC (rev 9895) +++ trunk/Hibernate3/test/org/hibernate/test/hql/Vehicle.hbm.xml 2006-05-05 20:29:41 UTC (rev 9896) @@ -24,4 +24,8 @@ </union-subclass> </class> + <sql-query name="native-delete-car"> + <synchronize table="Car"/> + delete from CAR where owner = ? + </sql-query> </hibernate-mapping> \ No newline at end of file |