Skip to content

Commit 34a46e3

Browse files
committed
Add SAP HANA batcher
1 parent ed54937 commit 34a46e3

File tree

4 files changed

+314
-4
lines changed

4 files changed

+314
-4
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Data.Common;
4+
using System.Diagnostics;
5+
using System.Text;
6+
using NHibernate.AdoNet.Util;
7+
using NHibernate.Exceptions;
8+
9+
namespace NHibernate.AdoNet
10+
{
11+
/// <summary>
12+
/// Summary description for HanaBatchingBatcher.
13+
/// By Jonathan Bregler
14+
/// </summary>
15+
public partial class HanaBatchingBatcher : AbstractBatcher
16+
{
17+
private int _batchSize;
18+
private int _countOfCommands;
19+
private int _totalExpectedRowsAffected;
20+
private DbCommand _currentBatch;
21+
private readonly IList<DbCommand> _currentBatchCommands = new List<DbCommand>();
22+
private StringBuilder _currentBatchCommandsLog;
23+
24+
public HanaBatchingBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
25+
: base(connectionManager, interceptor)
26+
{
27+
_batchSize = Factory.Settings.AdoBatchSize;
28+
//we always create this, because we need to deal with a scenario in which
29+
//the user change the logging configuration at runtime. Trying to put this
30+
//behind an if(log.IsDebugEnabled) will cause a null reference exception
31+
//at that point.
32+
_currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
33+
}
34+
35+
public override void AddToBatch(IExpectation expectation)
36+
{
37+
Debug.Assert(CurrentCommand is ICloneable); // HanaCommands are cloneable
38+
39+
var batchUpdate = CurrentCommand;
40+
Prepare(batchUpdate);
41+
Driver.AdjustCommand(batchUpdate);
42+
43+
_totalExpectedRowsAffected += expectation.ExpectedRowCount;
44+
string lineWithParameters = null;
45+
var sqlStatementLogger = Factory.Settings.SqlStatementLogger;
46+
if (sqlStatementLogger.IsDebugEnabled || Log.IsDebugEnabled())
47+
{
48+
lineWithParameters = sqlStatementLogger.GetCommandLineWithParameters(batchUpdate);
49+
var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic);
50+
lineWithParameters = formatStyle.Formatter.Format(lineWithParameters);
51+
_currentBatchCommandsLog.Append("command ")
52+
.Append(_countOfCommands)
53+
.Append(":")
54+
.AppendLine(lineWithParameters);
55+
}
56+
if (Log.IsDebugEnabled())
57+
{
58+
Log.Debug("Adding to batch:{0}", lineWithParameters);
59+
}
60+
61+
if (_currentBatch == null)
62+
{
63+
// use first command as the batching command
64+
_currentBatch = (batchUpdate as ICloneable).Clone() as DbCommand;
65+
}
66+
67+
_currentBatchCommands.Add((batchUpdate as ICloneable).Clone() as DbCommand);
68+
69+
_countOfCommands++;
70+
71+
if (_countOfCommands >= _batchSize)
72+
{
73+
DoExecuteBatch(batchUpdate);
74+
}
75+
}
76+
77+
protected override void DoExecuteBatch(DbCommand ps)
78+
{
79+
Log.Info("Executing batch");
80+
CheckReaders();
81+
82+
if (Factory.Settings.SqlStatementLogger.IsDebugEnabled)
83+
{
84+
Factory.Settings.SqlStatementLogger.LogBatchCommand(_currentBatchCommandsLog.ToString());
85+
_currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
86+
}
87+
88+
try
89+
{
90+
int rowCount = 0;
91+
92+
if (_countOfCommands > 0)
93+
{
94+
_currentBatch.Parameters.Clear();
95+
96+
foreach (var command in _currentBatchCommands)
97+
{
98+
foreach (DbParameter parameter in command.Parameters)
99+
{
100+
_currentBatch.Parameters.Add(parameter);
101+
}
102+
}
103+
104+
_currentBatch.Prepare();
105+
106+
try
107+
{
108+
rowCount = _currentBatch.ExecuteNonQuery();
109+
}
110+
catch (DbException e)
111+
{
112+
throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command.");
113+
}
114+
}
115+
116+
Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowCount);
117+
}
118+
finally
119+
{
120+
// Cleaning up even if batched outcome is invalid
121+
_totalExpectedRowsAffected = 0;
122+
_countOfCommands = 0;
123+
CloseBatchCommands();
124+
}
125+
}
126+
127+
protected override int CountOfStatementsInCurrentBatch
128+
{
129+
get { return _countOfCommands; }
130+
}
131+
132+
public override int BatchSize
133+
{
134+
get { return _batchSize; }
135+
set { _batchSize = value; }
136+
}
137+
138+
public override void CloseCommands()
139+
{
140+
base.CloseCommands();
141+
142+
CloseBatchCommands();
143+
}
144+
145+
protected override void Dispose(bool isDisposing)
146+
{
147+
base.Dispose(isDisposing);
148+
149+
CloseBatchCommands();
150+
}
151+
152+
private void CloseBatchCommands()
153+
{
154+
try
155+
{
156+
foreach (var currentBatchCommand in _currentBatchCommands)
157+
{
158+
currentBatchCommand.Dispose();
159+
}
160+
_currentBatchCommands.Clear();
161+
162+
_currentBatch?.Dispose();
163+
_currentBatch = null;
164+
}
165+
catch (Exception e)
166+
{
167+
// Prevent exceptions when clearing the batch from hiding any original exception
168+
// (We do not know here if this batch closing occurs after a failure or not.)
169+
Log.Warn(e, "Exception clearing batch");
170+
}
171+
}
172+
}
173+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using NHibernate.Engine;
2+
3+
namespace NHibernate.AdoNet
4+
{
5+
public class HanaBatchingBatcherFactory : IBatcherFactory
6+
{
7+
public virtual IBatcher CreateBatcher(ConnectionManager connectionManager, IInterceptor interceptor)
8+
{
9+
return new HanaBatchingBatcher(connectionManager, interceptor);
10+
}
11+
}
12+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System;
12+
using System.Collections.Generic;
13+
using System.Data.Common;
14+
using System.Diagnostics;
15+
using System.Text;
16+
using NHibernate.AdoNet.Util;
17+
using NHibernate.Exceptions;
18+
19+
namespace NHibernate.AdoNet
20+
{
21+
using System.Threading.Tasks;
22+
using System.Threading;
23+
public partial class HanaBatchingBatcher : AbstractBatcher
24+
{
25+
26+
public override async Task AddToBatchAsync(IExpectation expectation, CancellationToken cancellationToken)
27+
{
28+
cancellationToken.ThrowIfCancellationRequested();
29+
Debug.Assert(CurrentCommand is ICloneable); // HanaCommands are cloneable
30+
31+
var batchUpdate = CurrentCommand;
32+
await (PrepareAsync(batchUpdate, cancellationToken)).ConfigureAwait(false);
33+
Driver.AdjustCommand(batchUpdate);
34+
35+
_totalExpectedRowsAffected += expectation.ExpectedRowCount;
36+
string lineWithParameters = null;
37+
var sqlStatementLogger = Factory.Settings.SqlStatementLogger;
38+
if (sqlStatementLogger.IsDebugEnabled || Log.IsDebugEnabled())
39+
{
40+
lineWithParameters = sqlStatementLogger.GetCommandLineWithParameters(batchUpdate);
41+
var formatStyle = sqlStatementLogger.DetermineActualStyle(FormatStyle.Basic);
42+
lineWithParameters = formatStyle.Formatter.Format(lineWithParameters);
43+
_currentBatchCommandsLog.Append("command ")
44+
.Append(_countOfCommands)
45+
.Append(":")
46+
.AppendLine(lineWithParameters);
47+
}
48+
if (Log.IsDebugEnabled())
49+
{
50+
Log.Debug("Adding to batch:{0}", lineWithParameters);
51+
}
52+
53+
if (_currentBatch == null)
54+
{
55+
// use first command as the batching command
56+
_currentBatch = (batchUpdate as ICloneable).Clone() as DbCommand;
57+
}
58+
59+
_currentBatchCommands.Add((batchUpdate as ICloneable).Clone() as DbCommand);
60+
61+
_countOfCommands++;
62+
63+
if (_countOfCommands >= _batchSize)
64+
{
65+
await (DoExecuteBatchAsync(batchUpdate, cancellationToken)).ConfigureAwait(false);
66+
}
67+
}
68+
69+
protected override async Task DoExecuteBatchAsync(DbCommand ps, CancellationToken cancellationToken)
70+
{
71+
cancellationToken.ThrowIfCancellationRequested();
72+
Log.Info("Executing batch");
73+
await (CheckReadersAsync(cancellationToken)).ConfigureAwait(false);
74+
75+
if (Factory.Settings.SqlStatementLogger.IsDebugEnabled)
76+
{
77+
Factory.Settings.SqlStatementLogger.LogBatchCommand(_currentBatchCommandsLog.ToString());
78+
_currentBatchCommandsLog = new StringBuilder().AppendLine("Batch commands:");
79+
}
80+
81+
try
82+
{
83+
int rowCount = 0;
84+
85+
if (_countOfCommands > 0)
86+
{
87+
_currentBatch.Parameters.Clear();
88+
89+
foreach (var command in _currentBatchCommands)
90+
{
91+
foreach (DbParameter parameter in command.Parameters)
92+
{
93+
_currentBatch.Parameters.Add(parameter);
94+
}
95+
}
96+
97+
_currentBatch.Prepare();
98+
99+
try
100+
{
101+
rowCount = await (_currentBatch.ExecuteNonQueryAsync(cancellationToken)).ConfigureAwait(false);
102+
}
103+
catch (DbException e)
104+
{
105+
throw ADOExceptionHelper.Convert(Factory.SQLExceptionConverter, e, "could not execute batch command.");
106+
}
107+
}
108+
109+
Expectations.VerifyOutcomeBatched(_totalExpectedRowsAffected, rowCount);
110+
}
111+
finally
112+
{
113+
// Cleaning up even if batched outcome is invalid
114+
_totalExpectedRowsAffected = 0;
115+
_countOfCommands = 0;
116+
CloseBatchCommands();
117+
}
118+
}
119+
}
120+
}

src/NHibernate/Driver/HanaDriver.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using NHibernate.AdoNet;
23

34
namespace NHibernate.Driver
@@ -61,21 +62,25 @@ public HanaDriver() : base(
6162
/// <remarks>
6263
/// Sql Server uses <c>"@"</c> and Oracle uses <c>":"</c>.
6364
/// </remarks>
64-
public override string NamedPrefix => null;
65+
public override string NamedPrefix => String.Empty;
6566

66-
public override bool SupportsMultipleOpenReaders => true;
67+
public override bool SupportsMultipleOpenReaders => false;
6768

6869
public override IResultSetsCommand GetResultSetsCommand(Engine.ISessionImplementor session)
6970
{
7071
return new BasicResultSetsCommand(session);
7172
}
7273

73-
public override bool SupportsSystemTransactions => false;
74+
public override bool SupportsSystemTransactions => true;
7475

7576
public override bool SupportsNullEnlistment => false;
7677

7778
public override bool RequiresTimeSpanForTime => true;
7879

79-
System.Type IEmbeddedBatcherFactoryProvider.BatcherFactoryClass => typeof(NonBatchingBatcherFactory);
80+
public override bool HasDelayedDistributedTransactionCompletion => false;
81+
82+
public override bool SupportsEnlistmentWhenAutoEnlistmentIsDisabled => false;
83+
84+
System.Type IEmbeddedBatcherFactoryProvider.BatcherFactoryClass => typeof(HanaBatchingBatcherFactory);
8085
}
8186
}

0 commit comments

Comments
 (0)