|
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.
|