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