From: <fab...@us...> - 2009-05-04 22:01:27
|
Revision: 4231 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=4231&view=rev Author: fabiomaulo Date: 2009-05-04 22:01:16 +0000 (Mon, 04 May 2009) Log Message: ----------- Continue porting HQL executable (multi-table support) Modified Paths: -------------- trunk/nhibernate/src/NHibernate/AdoNet/ConnectionManager.cs trunk/nhibernate/src/NHibernate/Dialect/MsSql2000Dialect.cs trunk/nhibernate/src/NHibernate/Engine/Transaction/IIsolatedWork.cs trunk/nhibernate/src/NHibernate/Engine/TransactionHelper.cs trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/BasicExecutor.cs trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs trunk/nhibernate/src/NHibernate/NHibernate.csproj trunk/nhibernate/src/NHibernate/SqlCommand/SqlDeleteBuilder.cs trunk/nhibernate/src/NHibernate/SqlCommand/SqlSelectBuilder.cs trunk/nhibernate/src/NHibernate.Test/HQL/Ast/Animal.cs trunk/nhibernate/src/NHibernate.Test/HQL/Ast/BulkManipulation.cs trunk/nhibernate/src/NHibernate.Test/HQL/Ast/Vehicle.hbm.xml Added Paths: ----------- trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/MultiTableDeleteExecutor.cs trunk/nhibernate/src/NHibernate/SqlCommand/InsertSelect.cs Modified: trunk/nhibernate/src/NHibernate/AdoNet/ConnectionManager.cs =================================================================== --- trunk/nhibernate/src/NHibernate/AdoNet/ConnectionManager.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/AdoNet/ConnectionManager.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -414,5 +414,12 @@ manager.flushingFromDtcTransaction = false; } } + + public IDbCommand CreateCommand() + { + var result = GetConnection().CreateCommand(); + Transaction.Enlist(result); + return result; + } } } Modified: trunk/nhibernate/src/NHibernate/Dialect/MsSql2000Dialect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Dialect/MsSql2000Dialect.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/Dialect/MsSql2000Dialect.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -276,6 +276,21 @@ get { return true; } } + public override bool SupportsTemporaryTables + { + get { return true; } + } + + public override string GenerateTemporaryTableName(string baseTableName) + { + return "#" + baseTableName; + } + + public override bool DropTemporaryTableAfterUse() + { + return true; + } + /// <summary> /// /// </summary> Modified: trunk/nhibernate/src/NHibernate/Engine/Transaction/IIsolatedWork.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Engine/Transaction/IIsolatedWork.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/Engine/Transaction/IIsolatedWork.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -12,7 +12,10 @@ /// <summary> /// Perform the actual work to be done. /// </summary> - /// <param name="connection">The ADP cpnnection to use.</param> + /// <param name="connection">The ADP connection to use.</param> void DoWork(IDbConnection connection); + + // 2009-05-04 Another time we need a TransactionManager to manage isolated + // work for a given connection. } } \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/Engine/TransactionHelper.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Engine/TransactionHelper.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/Engine/TransactionHelper.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -1,4 +1,5 @@ using System.Data; +using System.Data.Common; using NHibernate.Engine.Transaction; using NHibernate.Exceptions; @@ -30,7 +31,7 @@ { generatedValue = owner.DoWorkInCurrentTransaction(connection, null); } - catch (System.Data.OleDb.OleDbException sqle) + catch (DbException sqle) { throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, sqle, "could not get or update next value", null); } Modified: trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -1,10 +1,18 @@ using System; +using System.Data; +using Antlr.Runtime.Tree; using NHibernate.Action; using NHibernate.Engine; +using NHibernate.Engine.Transaction; using NHibernate.Event; +using NHibernate.Hql.Ast.ANTLR.Tree; using NHibernate.Persister.Entity; using NHibernate.SqlCommand; using log4net; +using Antlr.Runtime; +using NHibernate.SqlTypes; +using NHibernate.Util; +using NHibernate.AdoNet.Util; namespace NHibernate.Hql.Ast.ANTLR.Exec { @@ -13,20 +21,27 @@ { private readonly ILog log; - protected AbstractStatementExecutor(HqlSqlWalker walker, ILog log) + protected AbstractStatementExecutor(IStatement statement, ILog log) { - Walker = walker; + Statement = statement; + Walker = statement.Walker; this.log = log; } protected HqlSqlWalker Walker { get; private set; } + protected IStatement Statement { get; private set; } - public abstract SqlString[] SqlStatements{get;} + public abstract SqlString[] SqlStatements { get; } public abstract int Execute(QueryParameters parameters, ISessionImplementor session); protected abstract IQueryable[] AffectedQueryables { get; } + protected ISessionFactoryImplementor Factory + { + get{return Walker.SessionFactoryHelper.Factory;} + } + protected virtual void CoordinateSharedCacheCleanup(ISessionImplementor session) { var action = new BulkOperationCleanupAction(session, AffectedQueryables); @@ -38,5 +53,248 @@ ((IEventSource)session).ActionQueue.AddAction(action); } } + + protected SqlString GenerateIdInsertSelect(IQueryable persister, string tableAlias, IASTNode whereClause) + { + var select = new SqlSelectBuilder(Factory); + SelectFragment selectFragment = new SelectFragment(Factory.Dialect) + .AddColumns(tableAlias, persister.IdentifierColumnNames, persister.IdentifierColumnNames); + select.SetSelectClause(selectFragment.ToFragmentString().Substring(2)); + + string rootTableName = persister.TableName; + SqlString fromJoinFragment = persister.FromJoinFragment(tableAlias, true, false); + SqlString whereJoinFragment = persister.WhereJoinFragment(tableAlias, true, false); + + select.SetFromClause(rootTableName + ' ' + tableAlias + fromJoinFragment); + + if (whereJoinFragment == null) + { + whereJoinFragment = SqlString.Empty; + } + else + { + whereJoinFragment = whereJoinFragment.Trim(); + if (whereJoinFragment.StartsWithCaseInsensitive("and ")) + { + whereJoinFragment = whereJoinFragment.Substring(4); + } + } + + SqlString userWhereClause = SqlString.Empty; + if (whereClause.ChildCount != 0) + { + // If a where clause was specified in the update/delete query, use it to limit the + // returned ids here... + try + { + var nodes = new CommonTreeNodeStream(whereClause); + var gen = new SqlGenerator(Factory, nodes); + gen.whereClause(); + userWhereClause = gen.GetSQL().Substring(7); + } + catch (RecognitionException e) + { + throw new HibernateException("Unable to generate id select for DML operation", e); + } + if (whereJoinFragment.Length > 0) + { + whereJoinFragment.Append(" and "); + } + } + + select.SetWhereClause(whereJoinFragment + userWhereClause); + + var insert = new InsertSelect(); + if (Factory.Settings.IsCommentsEnabled) + { + insert.SetComment("insert-select for " + persister.EntityName + " ids"); + } + insert.SetTableName(persister.TemporaryIdTableName); + insert.SetSelect(select); + return insert.ToSqlString(); + } + + protected string GenerateIdSubselect(IQueryable persister) + { + return "select " + StringHelper.Join(", ", persister.IdentifierColumnNames) + " from " + persister.TemporaryIdTableName; + } + + protected virtual void CreateTemporaryTableIfNecessary(IQueryable persister, ISessionImplementor session) + { + // Don't really know all the codes required to adequately decipher returned ADO exceptions here. + // simply allow the failure to be eaten and the subsequent insert-selects/deletes should fail + IIsolatedWork work = new TmpIdTableCreationIsolatedWork(persister, log, session); + if (ShouldIsolateTemporaryTableDDL()) + { + if (Factory.Settings.IsDataDefinitionInTransactionSupported) + { + Isolater.DoIsolatedWork(work, session); + } + else + { + Isolater.DoNonTransactedWork(work, session); + } + } + else + { + work.DoWork(session.ConnectionManager.GetConnection()); + session.ConnectionManager.AfterStatement(); + } + } + + protected virtual bool ShouldIsolateTemporaryTableDDL() + { + bool? dialectVote = Factory.Dialect.PerformTemporaryTableDDLInIsolation(); + if (dialectVote.HasValue) + { + return dialectVote.Value; + } + else + { + return Factory.Settings.IsDataDefinitionImplicitCommit; + } + } + + protected virtual void DropTemporaryTableIfNecessary(IQueryable persister, ISessionImplementor session) + { + if (Factory.Dialect.DropTemporaryTableAfterUse()) + { + IIsolatedWork work = new TmpIdTableDropIsolatedWork(persister, log, session); + + if (ShouldIsolateTemporaryTableDDL()) + { + if (Factory.Settings.IsDataDefinitionInTransactionSupported) + { + Isolater.DoIsolatedWork(work, session); + } + else + { + Isolater.DoNonTransactedWork(work, session); + } + } + else + { + work.DoWork(session.ConnectionManager.GetConnection()); + session.ConnectionManager.AfterStatement(); + } + } + else + { + // at the very least cleanup the data :) + IDbCommand ps = null; + try + { + var commandText = new SqlString("delete from " + persister.TemporaryIdTableName); + ps = session.Batcher.PrepareCommand(CommandType.Text, commandText, new SqlType[0]); + ps.ExecuteNonQuery(); + } + catch (Exception t) + { + log.Warn("unable to cleanup temporary id table after use [" + t + "]"); + } + finally + { + if (ps != null) + { + try + { + session.Batcher.CloseCommand(ps, null); + } + catch (Exception) + { + // ignore + } + } + } + } + } + + private class TmpIdTableCreationIsolatedWork : IIsolatedWork + { + private readonly IQueryable persister; + private readonly ILog log; + private readonly ISessionImplementor session; + + public TmpIdTableCreationIsolatedWork(IQueryable persister, ILog log, ISessionImplementor session) + { + this.persister = persister; + this.log = log; + this.session = session; + } + + public void DoWork(IDbConnection connection) + { + IDbCommand stmnt = null; + try + { + stmnt = session.ConnectionManager.CreateCommand(); + stmnt.CommandText = persister.TemporaryIdTableDDL; + stmnt.ExecuteNonQuery(); + session.Factory.Settings.SqlStatementLogger.LogCommand(stmnt, FormatStyle.Ddl); + } + catch (Exception t) + { + log.Debug("unable to create temporary id table [" + t.Message + "]"); + } + finally + { + if (stmnt != null) + { + try + { + stmnt.Dispose(); + } + catch (Exception) + { + // ignore + } + } + } + } + } + + private class TmpIdTableDropIsolatedWork : IIsolatedWork + { + public TmpIdTableDropIsolatedWork(IQueryable persister, ILog log, ISessionImplementor session) + { + this.persister = persister; + this.log = log; + this.session = session; + } + + private readonly IQueryable persister; + private readonly ILog log; + private readonly ISessionImplementor session; + + public void DoWork(IDbConnection connection) + { + IDbCommand stmnt = null; + try + { + stmnt = session.ConnectionManager.CreateCommand(); + stmnt.CommandText = "drop table " + persister.TemporaryIdTableName; + stmnt.ExecuteNonQuery(); + session.Factory.Settings.SqlStatementLogger.LogCommand(stmnt, FormatStyle.Ddl); + } + catch (Exception t) + { + log.Warn("unable to drop temporary id table after use [" + t.Message + "]"); + } + finally + { + if (stmnt != null) + { + try + { + stmnt.Dispose(); + } + catch (Exception) + { + // ignore + } + } + } + } + } } } \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/BasicExecutor.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/BasicExecutor.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/BasicExecutor.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -18,11 +18,11 @@ public class BasicExecutor : AbstractStatementExecutor { private readonly IQueryable persister; - private static readonly ILog log = LogManager.GetLogger(typeof(QueryTranslatorImpl)); + private static readonly ILog log = LogManager.GetLogger(typeof(BasicExecutor)); private readonly SqlString sql; public BasicExecutor(IStatement statement, ITokenStream tokenStream, IQueryable persister) - : base(statement.Walker, log) + : base(statement, log) { this.persister = persister; try @@ -41,14 +41,6 @@ protected IList<IParameterSpecification> Parameters{get;private set;} - protected ISessionFactoryImplementor Factory - { - get - { - return Walker.SessionFactoryHelper.Factory; - } - } - public override SqlString[] SqlStatements { get { return new[] {sql}; } @@ -72,7 +64,8 @@ } st = session.Batcher.PrepareCommand(CommandType.Text, sql, parameterTypes.ToArray()); IEnumerator<IParameterSpecification> paramSpecifications = Parameters.GetEnumerator(); - int pos = 1; + // NH Different behavior: The inital value is 0 (initialized to 1 in JAVA) + int pos = 0; while (paramSpecifications.MoveNext()) { var paramSpec = paramSpecifications.Current; Added: trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/MultiTableDeleteExecutor.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/MultiTableDeleteExecutor.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/MultiTableDeleteExecutor.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using log4net; +using NHibernate.Engine; +using NHibernate.Exceptions; +using NHibernate.Hql.Ast.ANTLR.Tree; +using NHibernate.Param; +using NHibernate.Persister.Entity; +using NHibernate.SqlCommand; +using NHibernate.SqlTypes; +using NHibernate.Util; + +namespace NHibernate.Hql.Ast.ANTLR.Exec +{ + public class MultiTableDeleteExecutor : AbstractStatementExecutor + { + private static readonly ILog log = LogManager.GetLogger(typeof(MultiTableDeleteExecutor)); + private readonly IQueryable persister; + private readonly SqlString idInsertSelect; + private readonly SqlString[] deletes; + + public MultiTableDeleteExecutor(IStatement statement) + : base(statement, log) + { + if (!Factory.Dialect.SupportsTemporaryTables) + { + throw new HibernateException("cannot perform multi-table deletes using dialect not supporting temp tables"); + } + + var deleteStatement = (DeleteStatement) statement; + + FromElement fromElement = deleteStatement.FromClause.GetFromElement(); + string bulkTargetAlias = fromElement.TableAlias; + persister = fromElement.Queryable; + + idInsertSelect = GenerateIdInsertSelect(persister, bulkTargetAlias, deleteStatement.WhereClause); + log.Debug("Generated ID-INSERT-SELECT SQL (multi-table delete) : " + idInsertSelect); + + string[] tableNames = persister.ConstraintOrderedTableNameClosure; + string[][] columnNames = persister.ContraintOrderedTableKeyColumnClosure; + string idSubselect = GenerateIdSubselect(persister); + + deletes = new SqlString[tableNames.Length]; + for (int i = tableNames.Length - 1; i >= 0; i--) + { + // TODO : an optimization here would be to consider cascade deletes and not gen those delete statements; + // the difficulty is the ordering of the tables here vs the cascade attributes on the persisters -> + // the table info gotten here should really be self-contained (i.e., a class representation + // defining all the needed attributes), then we could then get an array of those + SqlDeleteBuilder delete = new SqlDeleteBuilder(Factory.Dialect, Factory) + .SetTableName(tableNames[i]) + .SetWhere("(" + StringHelper.Join(", ", columnNames[i]) + ") IN (" + idSubselect + ")"); + if (Factory.Settings.IsCommentsEnabled) + { + delete.SetComment("bulk delete"); + } + + deletes[i] = delete.ToSqlString(); + } + } + + public override SqlString[] SqlStatements + { + get { return deletes; } + } + + public override int Execute(QueryParameters parameters, ISessionImplementor session) + { + CoordinateSharedCacheCleanup(session); + + CreateTemporaryTableIfNecessary(persister, session); + + try + { + // First, save off the pertinent ids, saving the number of pertinent ids for return + IDbCommand ps = null; + int resultCount; + try + { + try + { + var parameterTypes = new List<SqlType>(Walker.Parameters.Count); + foreach (var parameterSpecification in Walker.Parameters) + { + parameterTypes.AddRange(parameterSpecification.ExpectedType.SqlTypes(Factory)); + } + + ps = session.Batcher.PrepareCommand(CommandType.Text, idInsertSelect, parameterTypes.ToArray()); + IEnumerator<IParameterSpecification> paramSpecifications = Walker.Parameters.GetEnumerator(); + // NH Different behavior: The inital value is 0 (initialized to 1 in JAVA) + int pos = 0; + while (paramSpecifications.MoveNext()) + { + var paramSpec = paramSpecifications.Current; + pos += paramSpec.Bind(ps, parameters, session, pos); + } + resultCount = session.Batcher.ExecuteNonQuery(ps); + } + finally + { + if (ps != null) + { + session.Batcher.CloseCommand(ps, null); + } + } + } + catch (DbException e) + { + throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not insert/select ids for bulk delete", idInsertSelect); + } + + // Start performing the deletes + for (int i = 0; i < deletes.Length; i++) + { + try + { + try + { + ps = session.Batcher.PrepareCommand(CommandType.Text, deletes[i], new SqlType[0]); + session.Batcher.ExecuteNonQuery(ps); + } + finally + { + if (ps != null) + { + session.Batcher.CloseCommand(ps, null); + } + } + } + catch (DbException e) + { + throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "error performing bulk delete", deletes[i]); + } + } + + return resultCount; + } + finally + { + DropTemporaryTableIfNecessary(persister, session); + } + } + + protected override IQueryable[] AffectedQueryables + { + get { return new[] { persister }; } + } + } +} \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs =================================================================== --- trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -403,8 +403,7 @@ IQueryable persister = fromElement.Queryable; if (persister.IsMultiTable) { - throw new NotSupportedException(); - //return new MultiTableDeleteExecutor(walker); + return new MultiTableDeleteExecutor(statement); } else { @@ -420,8 +419,7 @@ // even here, if only properties mapped to the "base table" are referenced // in the set and where clauses, this could be handled by the BasicDelegate. // TODO : decide if it is better performance-wise to perform that check, or to simply use the MultiTableUpdateDelegate - throw new NotSupportedException(); - //return new MultiTableUpdateExecutor(walker); + return new MultiTableDeleteExecutor(statement); } else { Modified: trunk/nhibernate/src/NHibernate/NHibernate.csproj =================================================================== --- trunk/nhibernate/src/NHibernate/NHibernate.csproj 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/NHibernate.csproj 2009-05-04 22:01:16 UTC (rev 4231) @@ -483,6 +483,7 @@ <Compile Include="Hql\Ast\ANTLR\Exec\AbstractStatementExecutor.cs" /> <Compile Include="Hql\Ast\ANTLR\Exec\BasicExecutor.cs" /> <Compile Include="Hql\Ast\ANTLR\Exec\IStatementExecutor.cs" /> + <Compile Include="Hql\Ast\ANTLR\Exec\MultiTableDeleteExecutor.cs" /> <Compile Include="Hql\Ast\ANTLR\Generated\HqlLexer.cs" /> <Compile Include="Hql\Ast\ANTLR\Generated\HqlParser.cs" /> <Compile Include="Hql\Ast\ANTLR\Generated\HqlSqlWalker.cs" /> @@ -584,6 +585,7 @@ <Compile Include="Hql\Ast\ANTLR\Util\JoinProcessor.cs" /> <Compile Include="Hql\Ast\ANTLR\Util\LiteralProcessor.cs" /> <Compile Include="Hql\Ast\ANTLR\Util\NodeTraverser.cs" /> + <Compile Include="SqlCommand\InsertSelect.cs" /> <Compile Include="Util\NullableDictionary.cs" /> <Compile Include="Hql\Ast\ANTLR\Util\PathHelper.cs" /> <Compile Include="Hql\Ast\ANTLR\Util\SyntheticAndFactory.cs" /> Added: trunk/nhibernate/src/NHibernate/SqlCommand/InsertSelect.cs =================================================================== --- trunk/nhibernate/src/NHibernate/SqlCommand/InsertSelect.cs (rev 0) +++ trunk/nhibernate/src/NHibernate/SqlCommand/InsertSelect.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using log4net; +using NHibernate.Engine; + +namespace NHibernate.SqlCommand +{ + public class InsertSelect : ISqlStringBuilder + { + private static readonly ILog log = LogManager.GetLogger(typeof(InsertSelect)); + + private readonly ISessionFactoryImplementor factory; + private string tableName; + private string comment; + private readonly List<string> columnNames = new List<string>(); + private SqlSelectBuilder select; + + public virtual InsertSelect SetTableName(string tableName) + { + this.tableName = tableName; + return this; + } + + public virtual InsertSelect SetComment(string comment) + { + this.comment = comment; + return this; + } + + public virtual InsertSelect AddColumn(string columnName) + { + columnNames.Add(columnName); + return this; + } + + public virtual InsertSelect AddColumns(string[] columnNames) + { + this.columnNames.AddRange(columnNames); + return this; + } + + public virtual InsertSelect SetSelect(SqlSelectBuilder select) + { + this.select = select; + return this; + } + + public SqlString ToSqlString() + { + if (tableName == null) + throw new HibernateException("no table name defined for insert-select"); + if (select == null) + throw new HibernateException("no select defined for insert-select"); + + var buf = new SqlStringBuilder(columnNames.Count + 4); + if (comment != null) + { + buf.Add("/* " + comment + " */ "); + } + buf.Add("insert into ").Add(tableName); + if (!(columnNames.Count == 0)) + { + buf.Add(" ("); + bool commaNeeded= false; + foreach (var columnName in columnNames) + { + if(commaNeeded) + { + buf.Add(", "); + } + buf.Add(columnName); + commaNeeded = true; + } + buf.Add(")"); + } + buf.Add(" ").Add(select.ToStatementString()); + return buf.ToSqlString(); + } + } +} \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate/SqlCommand/SqlDeleteBuilder.cs =================================================================== --- trunk/nhibernate/src/NHibernate/SqlCommand/SqlDeleteBuilder.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/SqlCommand/SqlDeleteBuilder.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -15,7 +15,7 @@ private static readonly ILog log = LogManager.GetLogger(typeof(SqlDeleteBuilder)); private string tableName; - private readonly List<SqlString> whereStrings = new List<SqlString>(); + private List<SqlString> whereStrings = new List<SqlString>(); private readonly List<SqlType> parameterTypes = new List<SqlType>(); private string comment; @@ -81,7 +81,6 @@ return this; } - public SqlDeleteBuilder AddWhereFragment(string columnName, SqlType type, string op) { if (!string.IsNullOrEmpty(columnName)) @@ -91,6 +90,7 @@ } return this; } + /// <summary> /// Adds a string to the WhereFragement /// </summary> @@ -104,6 +104,15 @@ return this; } + public virtual SqlDeleteBuilder SetWhere(string whereSql) + { + if (StringHelper.IsNotEmpty(whereSql)) + { + whereStrings = new List<SqlString>(new[]{ new SqlString(whereSql)}); + } + return this; + } + #region ISqlStringBuilder Members public SqlString ToSqlString() Modified: trunk/nhibernate/src/NHibernate/SqlCommand/SqlSelectBuilder.cs =================================================================== --- trunk/nhibernate/src/NHibernate/SqlCommand/SqlSelectBuilder.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate/SqlCommand/SqlSelectBuilder.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -207,7 +207,9 @@ // plus the number of parts in outerJoinsAfterWhere SqlString. // 1 = the whereClause // 2 = the "ORDER BY" and orderByClause - int initialCapacity = 4 + outerJoinsAfterFrom.Count + 1 + outerJoinsAfterWhere.Count + 1 + 2; + var joinAfterFrom = outerJoinsAfterFrom != null ? outerJoinsAfterFrom.Count : 0; + var joinAfterWhere = outerJoinsAfterWhere != null ? outerJoinsAfterWhere.Count : 0; + int initialCapacity = 4 + joinAfterFrom + 1 + joinAfterWhere + 1 + 2; if (!string.IsNullOrEmpty(comment)) initialCapacity++; @@ -218,9 +220,13 @@ sqlBuilder.Add("SELECT ") .Add(selectClause) .Add(" FROM ") - .Add(fromClause) - .Add(outerJoinsAfterFrom); + .Add(fromClause); + if (StringHelper.IsNotEmpty(outerJoinsAfterFrom)) + { + sqlBuilder.Add(outerJoinsAfterFrom); + } + if (StringHelper.IsNotEmpty(whereClause) || StringHelper.IsNotEmpty(outerJoinsAfterWhere)) { sqlBuilder.Add(" WHERE "); Modified: trunk/nhibernate/src/NHibernate.Test/HQL/Ast/Animal.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/HQL/Ast/Animal.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate.Test/HQL/Ast/Animal.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -60,5 +60,15 @@ get { return serialNumber; } set { serialNumber = value; } } + + public virtual void AddOffspring(Animal offSpring) + { + if (offspring == null) + { + offspring = new HashedSet(); + } + + offspring.Add(offSpring); + } } } \ No newline at end of file Modified: trunk/nhibernate/src/NHibernate.Test/HQL/Ast/BulkManipulation.cs =================================================================== --- trunk/nhibernate/src/NHibernate.Test/HQL/Ast/BulkManipulation.cs 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate.Test/HQL/Ast/BulkManipulation.cs 2009-05-04 22:01:16 UTC (rev 4231) @@ -33,7 +33,7 @@ #endregion - [Test, Ignore("Not supported")] + [Test] public void DeleteRestrictedOnManyToOne() { var data = new TestData(this); @@ -95,12 +95,12 @@ Frog = new Animal { BodyWeight = 34, Description = "Frog" }; Polliwog.Father = Frog; - Frog.Offspring.Add(Polliwog); + Frog.AddOffspring(Polliwog); Butterfly = new Animal { BodyWeight = 9, Description = "Butterfly" }; Catepillar.Mother = Butterfly; - Butterfly.Offspring.Add(Catepillar); + Butterfly.AddOffspring(Catepillar); s.Save(Frog); s.Save(Polliwog); Modified: trunk/nhibernate/src/NHibernate.Test/HQL/Ast/Vehicle.hbm.xml =================================================================== --- trunk/nhibernate/src/NHibernate.Test/HQL/Ast/Vehicle.hbm.xml 2009-05-04 13:34:20 UTC (rev 4230) +++ trunk/nhibernate/src/NHibernate.Test/HQL/Ast/Vehicle.hbm.xml 2009-05-04 22:01:16 UTC (rev 4231) @@ -6,7 +6,7 @@ <!-- Vehicle represents an abstract root of a union-subclass hierarchy --> <class name="Vehicle" abstract="true"> <id name="id" access="field" type="long"> - <generator class="increment"/> + <generator class="hilo"/> </id> <property name="Vin" type="string"/> <property name="Owner" type="string"/> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |