|
From: <fab...@us...> - 2009-05-05 06:10:40
|
Revision: 4243
http://nhibernate.svn.sourceforge.net/nhibernate/?rev=4243&view=rev
Author: fabiomaulo
Date: 2009-05-05 06:10:29 +0000 (Tue, 05 May 2009)
Log Message:
-----------
Continue porting HQL executable (update)
Modified Paths:
--------------
trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs
trunk/nhibernate/src/NHibernate/NHibernate.csproj
trunk/nhibernate/src/NHibernate/SqlCommand/InsertSelect.cs
trunk/nhibernate/src/NHibernate/SqlCommand/SqlUpdateBuilder.cs
Added Paths:
-----------
trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/MultiTableUpdateExecutor.cs
trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Tree/AssignmentSpecification.cs
Added: trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/MultiTableUpdateExecutor.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/MultiTableUpdateExecutor.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Exec/MultiTableUpdateExecutor.cs 2009-05-05 06:10:29 UTC (rev 4243)
@@ -0,0 +1,190 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+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
+{
+ [CLSCompliant(false)]
+ public class MultiTableUpdateExecutor : AbstractStatementExecutor
+ {
+ private static readonly ILog log = LogManager.GetLogger(typeof(MultiTableDeleteExecutor));
+ private readonly IQueryable persister;
+ private readonly SqlString idInsertSelect;
+ private readonly SqlString[] updates;
+ private readonly IParameterSpecification[][] hqlParameters;
+
+ public MultiTableUpdateExecutor(IStatement statement, ILog log) : base(statement, log)
+ {
+ if (!Factory.Dialect.SupportsTemporaryTables)
+ {
+ throw new HibernateException("cannot perform multi-table updates using dialect not supporting temp tables");
+ }
+ var updateStatement = (UpdateStatement)statement;
+
+ FromElement fromElement = updateStatement.FromClause.GetFromElement();
+ string bulkTargetAlias = fromElement.TableAlias;
+ persister = fromElement.Queryable;
+
+ idInsertSelect = GenerateIdInsertSelect(persister, bulkTargetAlias, updateStatement.WhereClause);
+ log.Debug("Generated ID-INSERT-SELECT SQL (multi-table update) : " + idInsertSelect);
+
+ string[] tableNames = persister.ConstraintOrderedTableNameClosure;
+ string[][] columnNames = persister.ContraintOrderedTableKeyColumnClosure;
+
+ string idSubselect = GenerateIdSubselect(persister);
+ var assignmentSpecifications = Walker.AssignmentSpecifications;
+
+ updates = new SqlString[tableNames.Length];
+ hqlParameters = new IParameterSpecification[tableNames.Length][];
+ for (int tableIndex = 0; tableIndex < tableNames.Length; tableIndex++)
+ {
+ bool affected = false;
+ var parameterList = new List<IParameterSpecification>();
+ SqlUpdateBuilder update = new SqlUpdateBuilder(Factory.Dialect, Factory)
+ .SetTableName(tableNames[tableIndex])
+ .SetWhere("(" + StringHelper.Join(", ", columnNames[tableIndex]) + ") IN (" + idSubselect + ")");
+
+ if (Factory.Settings.IsCommentsEnabled)
+ {
+ update.SetComment("bulk update");
+ }
+ foreach (var specification in assignmentSpecifications)
+ {
+ if (specification.AffectsTable(tableNames[tableIndex]))
+ {
+ affected = true;
+ update.AppendAssignmentFragment(specification.SqlAssignmentFragment);
+ if (specification.Parameters != null)
+ {
+ for (int paramIndex = 0; paramIndex < specification.Parameters.Length; paramIndex++)
+ {
+ parameterList.Add(specification.Parameters[paramIndex]);
+ }
+ }
+ }
+ }
+ if (affected)
+ {
+ updates[tableIndex] = update.ToSqlString();
+ hqlParameters[tableIndex] = parameterList.ToArray();
+ }
+ }
+ }
+
+ public override SqlString[] SqlStatements
+ {
+ get { return updates; }
+ }
+
+ public override int Execute(QueryParameters parameters, ISessionImplementor session)
+ {
+ CoordinateSharedCacheCleanup(session);
+
+ CreateTemporaryTableIfNecessary(persister, session);
+
+ try
+ {
+ // First, save off the pertinent ids, as the return value
+ 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());
+
+ int parameterStart = Walker.NumberOfParametersInSetClause;
+
+ var allParams = Walker.Parameters;
+
+ IEnumerator<IParameterSpecification> whereParams =
+ (new List<IParameterSpecification>(allParams)).GetRange(parameterStart, allParams.Count - parameterStart).
+ GetEnumerator();
+ // NH Different behavior: The inital value is 0 (initialized to 1 in JAVA)
+ int pos = 0;
+ while (whereParams.MoveNext())
+ {
+ var paramSpec = whereParams.Current;
+ pos += paramSpec.Bind(ps, parameters, session, pos);
+ }
+ resultCount = session.Batcher.ExecuteNonQuery(ps);
+ }
+ finally
+ {
+ if (ps != null)
+ {
+ session.Batcher.CloseCommand(ps, null);
+ }
+ }
+ }
+ catch (System.Data.OleDb.OleDbException e)
+ {
+ throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not insert/select ids for bulk update", idInsertSelect);
+ }
+
+ // Start performing the updates
+ for (int i = 0; i < updates.Length; i++)
+ {
+ if (updates[i] == null)
+ {
+ continue;
+ }
+ try
+ {
+ try
+ {
+ ps = session.Batcher.PrepareCommand(CommandType.Text, updates[i], new SqlType[0]);
+
+ if (hqlParameters[i] != null)
+ {
+ int position = 1; // ADO params are 0-based
+ for (int x = 0; x < hqlParameters[i].Length; x++)
+ {
+ position += hqlParameters[i][x].Bind(ps, parameters, session, position);
+ }
+ }
+ session.Batcher.ExecuteNonQuery(ps);
+ }
+ finally
+ {
+ if (ps != null)
+ {
+ session.Batcher.CloseCommand(ps, null);
+ }
+ }
+ }
+ catch (System.Data.OleDb.OleDbException e)
+ {
+ throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "error performing bulk update", updates[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/HqlSqlWalker.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs 2009-05-05 04:15:19 UTC (rev 4242)
+++ trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs 2009-05-05 06:10:29 UTC (rev 4243)
@@ -53,6 +53,8 @@
private IParseErrorHandler _parseErrorHandler = new ErrorCounter();
private IASTFactory _nodeFactory;
+ private readonly List<AssignmentSpecification> assignmentSpecifications = new List<AssignmentSpecification>();
+ private int numberOfParametersInSetClause;
public HqlSqlWalker(QueryTranslatorImpl qti,
ISessionFactoryImplementor sfi,
@@ -68,6 +70,16 @@
_collectionFilterRole = collectionRole;
}
+ public IList<AssignmentSpecification> AssignmentSpecifications
+ {
+ get { return assignmentSpecifications; }
+ }
+
+ public int NumberOfParametersInSetClause
+ {
+ get { return numberOfParametersInSetClause; }
+ }
+
public IParseErrorHandler ParseErrorHandler
{
get { return _parseErrorHandler; }
@@ -333,11 +345,31 @@
constructorNode.Prepare();
}
- static void EvaluateAssignment(IASTNode eq)
+ protected void EvaluateAssignment(IASTNode eq)
{
- throw new NotImplementedException(); // DML
+ PrepareLogicOperator(eq);
+ IQueryable persister = CurrentFromClause.GetFromElement().Queryable;
+ EvaluateAssignment(eq, persister, -1);
}
+ private void EvaluateAssignment(IASTNode eq, IQueryable persister, int targetIndex)
+ {
+ if (persister.IsMultiTable)
+ {
+ // no need to even collect this information if the persister is considered multi-table
+ var specification = new AssignmentSpecification(eq, persister);
+ if (targetIndex >= 0)
+ {
+ assignmentSpecifications.Insert(targetIndex, specification);
+ }
+ else
+ {
+ assignmentSpecifications.Add(specification);
+ }
+ numberOfParametersInSetClause += specification.Parameters.Length;
+ }
+ }
+
void BeforeSelectClause()
{
// Turn off includeSubclasses on all FromElements.
Added: trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Tree/AssignmentSpecification.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Tree/AssignmentSpecification.cs (rev 0)
+++ trunk/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Tree/AssignmentSpecification.cs 2009-05-05 06:10:29 UTC (rev 4243)
@@ -0,0 +1,143 @@
+using System;
+using Antlr.Runtime.Tree;
+using Iesi.Collections.Generic;
+using NHibernate.Engine;
+using NHibernate.Hql.Ast.ANTLR.Util;
+using NHibernate.Param;
+using NHibernate.Persister.Entity;
+using NHibernate.SqlCommand;
+
+namespace NHibernate.Hql.Ast.ANTLR.Tree
+{
+ /// <summary>
+ /// Encapsulates the information relating to an individual assignment within the
+ /// set clause of an HQL update statement. This information is used during execution
+ /// of the update statements when the updates occur against "multi-table" stuff.
+ /// </summary>
+ [CLSCompliant(false)]
+ public class AssignmentSpecification
+ {
+ private readonly IASTNode eq;
+ private readonly ISessionFactoryImplementor factory;
+ private readonly ISet<string> tableNames;
+ private readonly IParameterSpecification[] hqlParameters;
+ private SqlString sqlAssignmentString;
+
+ public AssignmentSpecification(IASTNode eq, IQueryable persister)
+ {
+ if (eq.Type != HqlSqlWalker.EQ)
+ {
+ throw new QueryException("assignment in set-clause not associated with equals");
+ }
+
+ this.eq = eq;
+ factory = persister.Factory;
+
+ // Needed to bump this up to DotNode, because that is the only thing which currently
+ // knows about the property-ref path in the correct format; it is either this, or
+ // recurse over the DotNodes constructing the property path just like DotNode does
+ // internally
+ var lhs = (DotNode)eq.GetFirstChild();
+ var rhs = (SqlNode)lhs.NextSibling;
+
+ ValidateLhs(lhs);
+
+ string propertyPath = lhs.PropertyPath;
+ var temp = new HashedSet<string>();
+ // yuck!
+ var usep = persister as UnionSubclassEntityPersister;
+ if (usep!=null)
+ {
+ temp.AddAll(persister.ConstraintOrderedTableNameClosure);
+ }
+ else
+ {
+ temp.Add(persister.GetSubclassTableName(persister.GetSubclassPropertyTableNumber(propertyPath)));
+ }
+ tableNames = new ImmutableSet<string>(temp);
+
+ if (rhs == null)
+ {
+ hqlParameters = new IParameterSpecification[0];
+ }
+ else if (IsParam(rhs))
+ {
+ hqlParameters = new[] { ((ParameterNode)rhs).HqlParameterSpecification };
+ }
+ else
+ {
+ var parameterList = ASTUtil.CollectChildren(rhs, IsParam);
+ hqlParameters = new IParameterSpecification[parameterList.Count];
+ int i = 0;
+ foreach (ParameterNode parameterNode in parameterList)
+ {
+ hqlParameters[i++] = parameterNode.HqlParameterSpecification;
+ }
+ }
+ }
+ public bool AffectsTable(string tableName)
+ {
+ return tableNames.Contains(tableName);
+ }
+
+ private static bool IsParam(IASTNode node)
+ {
+ return node.Type == HqlSqlWalker.PARAM || node.Type == HqlSqlWalker.NAMED_PARAM;
+ }
+
+ private static void ValidateLhs(FromReferenceNode lhs)
+ {
+ // make sure the lhs is "assignable"...
+ if (!lhs.IsResolved)
+ {
+ throw new NotSupportedException("cannot validate assignablity of unresolved node");
+ }
+
+ if (lhs.DataType.IsCollectionType)
+ {
+ throw new QueryException("collections not assignable in update statements");
+ }
+ else if (lhs.DataType.IsComponentType)
+ {
+ throw new QueryException("Components currently not assignable in update statements");
+ }
+ else if (lhs.DataType.IsEntityType)
+ {
+ // currently allowed...
+ }
+
+ // TODO : why aren't these the same?
+ if (lhs.GetImpliedJoin() != null || lhs.FromElement.IsImplied)
+ {
+ throw new QueryException("Implied join paths are not assignable in update statements");
+ }
+ }
+
+ public IParameterSpecification[] Parameters
+ {
+ get{return hqlParameters;}
+ }
+
+ public SqlString SqlAssignmentFragment
+ {
+ get
+ {
+ if (sqlAssignmentString == null)
+ {
+ try
+ {
+ var gen = new SqlGenerator(factory, new CommonTreeNodeStream(eq));
+ gen.comparisonExpr(false); // false indicates to not generate parens around the assignment
+ sqlAssignmentString = gen.GetSQL();
+ }
+ catch (Exception t)
+ {
+ throw new QueryException("cannot interpret set-clause assignment", t);
+ }
+ }
+ return sqlAssignmentString;
+ }
+
+ }
+ }
+}
\ No newline at end of file
Modified: trunk/nhibernate/src/NHibernate/NHibernate.csproj
===================================================================
--- trunk/nhibernate/src/NHibernate/NHibernate.csproj 2009-05-05 04:15:19 UTC (rev 4242)
+++ trunk/nhibernate/src/NHibernate/NHibernate.csproj 2009-05-05 06:10:29 UTC (rev 4243)
@@ -484,6 +484,7 @@
<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\Exec\MultiTableUpdateExecutor.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" />
@@ -497,6 +498,7 @@
<Compile Include="Hql\Ast\ANTLR\InvalidWithClauseException.cs" />
<Compile Include="Hql\Ast\ANTLR\IParseErrorHandler.cs" />
<Compile Include="Hql\Ast\ANTLR\Loader\QueryLoader.cs" />
+ <Compile Include="Hql\Ast\ANTLR\Tree\AssignmentSpecification.cs" />
<Compile Include="Hql\Ast\ANTLR\Tree\InsertStatement.cs" />
<Compile Include="Hql\Ast\ANTLR\Tree\UpdateStatement.cs" />
<Compile Include="Param\AbstractExplicitParameterSpecification.cs" />
Modified: trunk/nhibernate/src/NHibernate/SqlCommand/InsertSelect.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/SqlCommand/InsertSelect.cs 2009-05-05 04:15:19 UTC (rev 4242)
+++ trunk/nhibernate/src/NHibernate/SqlCommand/InsertSelect.cs 2009-05-05 06:10:29 UTC (rev 4243)
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using log4net;
-using NHibernate.Engine;
namespace NHibernate.SqlCommand
{
@@ -8,7 +7,6 @@
{
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>();
Modified: trunk/nhibernate/src/NHibernate/SqlCommand/SqlUpdateBuilder.cs
===================================================================
--- trunk/nhibernate/src/NHibernate/SqlCommand/SqlUpdateBuilder.cs 2009-05-05 04:15:19 UTC (rev 4242)
+++ trunk/nhibernate/src/NHibernate/SqlCommand/SqlUpdateBuilder.cs 2009-05-05 06:10:29 UTC (rev 4243)
@@ -20,8 +20,9 @@
// columns-> (ColumnName, Value) or (ColumnName, SqlType) for parametrized column
private readonly LinkedHashMap<string, object> columns = new LinkedHashMap<string, object>();
- private readonly List<SqlString> whereStrings = new List<SqlString>();
+ private List<SqlString> whereStrings = new List<SqlString>();
private readonly List<SqlType> whereParameterTypes = new List<SqlType>();
+ private SqlString assignments;
public SqlUpdateBuilder(Dialect.Dialect dialect, IMapping mapping)
: base(dialect, mapping) {}
@@ -120,6 +121,28 @@
return this;
}
+ public SqlUpdateBuilder AppendAssignmentFragment(SqlString fragment)
+ {
+ if (assignments == null)
+ {
+ assignments = fragment;
+ }
+ else
+ {
+ assignments.Append(", ").Append(fragment);
+ }
+ return this;
+ }
+
+ public SqlUpdateBuilder SetWhere(string whereSql)
+ {
+ if (StringHelper.IsNotEmpty(whereSql))
+ {
+ whereStrings = new List<SqlString>(new[] { new SqlString(whereSql) });
+ }
+ return this;
+ }
+
/// <summary>
/// Sets the IdentityColumn for the <c>UPDATE</c> sql to use.
/// </summary>
@@ -229,7 +252,7 @@
if (!string.IsNullOrEmpty(comment))
initialCapacity++;
- SqlStringBuilder sqlBuilder = new SqlStringBuilder(initialCapacity + 2);
+ var sqlBuilder = new SqlStringBuilder(initialCapacity + 2);
if (!string.IsNullOrEmpty(comment))
sqlBuilder.Add("/* " + comment + " */ ");
@@ -237,6 +260,7 @@
.Add(tableName)
.Add(" SET ");
+ bool assignmentsAppended = false;
bool commaNeeded = false;
foreach (KeyValuePair<string, object> valuePair in columns)
{
@@ -253,7 +277,16 @@
sqlBuilder.Add(Parameter.Placeholder);
else
sqlBuilder.Add((string) valuePair.Value);
+ assignmentsAppended = true;
}
+ if (assignments != null)
+ {
+ if (assignmentsAppended)
+ {
+ sqlBuilder.Add(", ");
+ }
+ sqlBuilder.Add(assignments);
+ }
sqlBuilder.Add(" WHERE ");
bool andNeeded = false;
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|